What is body-scroll-lock?
The body-scroll-lock package is designed to disable scrolling on the body element of a webpage, typically used when a modal or overlay is open to prevent background scrolling. It provides a simple API to lock and unlock scroll on touch and non-touch devices.
What are body-scroll-lock's main functionalities?
Disable scrolling on the body
This feature allows you to disable scrolling on the body of the page. The target element is typically the modal or overlay that is currently active.
import { disableBodyScroll } from 'body-scroll-lock';
const targetElement = document.querySelector('#someElementId');
disableBodyScroll(targetElement);
Enable scrolling on the body
This feature re-enables scrolling on the body of the page. It is used when the modal or overlay is closed and normal page interaction is to be restored.
import { enableBodyScroll } from 'body-scroll-lock';
const targetElement = document.querySelector('#someElementId');
enableBodyScroll(targetElement);
Clear all body scroll locks
This function clears all locks on body scroll. It is useful when you need to ensure that all locks have been removed, for instance, during cleanup or unmounting of components.
import { clearAllBodyScrollLocks } from 'body-scroll-lock';
clearAllBodyScrollLocks();
Other packages similar to body-scroll-lock
react-scrolllock
This package provides similar functionality to body-scroll-lock but is specifically tailored for React applications. It offers a React component that can be wrapped around parts of the UI to prevent scrolling.
scroll-lock
Similar to body-scroll-lock, scroll-lock is another package that prevents scrolling on the body element. It provides more customization options and methods for handling scroll locking.
no-scroll
no-scroll is a simpler and smaller package compared to body-scroll-lock. It provides basic functionality to stop scrolling on the body without additional features like handling touch events.
Why BSL?
Enables body scroll locking (for iOS Mobile and Tablet, Android, desktop Safari/Chrome/Firefox) without breaking scrolling of a target element (eg. modal/lightbox/flyouts/nav-menus).
Features:
- disables body scroll WITHOUT disabling scroll of a target element
- works on iOS mobile/tablet (!!)
- works on Android
- works on Safari desktop
- works on Chrome/Firefox
- works with vanilla JS and frameworks such as React / Angular / VueJS
- supports nested target elements (eg. a modal that appears on top of a flyout)
- can reserve scrollbar width
-webkit-overflow-scrolling: touch
still works
Aren't the alternative approaches sufficient?
- the approach
document.body.ontouchmove = (e) => { e.preventDefault(); return false; };
locks the
body scroll, but ALSO locks the scroll of a target element (eg. modal). - the approach
overflow: hidden
on the body or html elements doesn't work for all browsers - the
position: fixed
approach causes the body scroll to reset - some approaches break inertia/momentum/rubber-band scrolling on iOS
LIGHT Package Size:
Install
$ yarn add body-scroll-lock
or
$ npm install body-scroll-lock
You can also load via a <script src="lib/bodyScrollLock.js"></script>
tag (refer to the lib folder).
Usage examples
Common JS
const bodyScrollLock = require('body-scroll-lock');
const disableBodyScroll = bodyScrollLock.disableBodyScroll;
const enableBodyScroll = bodyScrollLock.enableBodyScroll;
const targetElement = document.querySelector('#someElementId');
disableBodyScroll(targetElement);
enableBodyScroll(targetElement);
React/ES6
import { disableBodyScroll, enableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';
class SomeComponent extends React.Component {
targetElement = null;
componentDidMount() {
this.targetElement = document.querySelector('#targetElementId');
}
showTargetElement = () => {
disableBodyScroll(this.targetElement);
};
hideTargetElement = () => {
enableBodyScroll(this.targetElement);
};
componentWillUnmount() {
clearAllBodyScrollLocks();
}
render() {
return <div>some JSX to go here</div>;
}
}
React/ES6 with Refs
import { disableBodyScroll, enableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';
class SomeComponent extends React.Component {
targetRef = React.createRef();
targetElement = null;
componentDidMount() {
this.targetElement = this.targetRef.current;
}
showTargetElement = () => {
disableBodyScroll(this.targetElement);
};
hideTargetElement = () => {
enableBodyScroll(this.targetElement);
};
componentWillUnmount() {
clearAllBodyScrollLocks();
}
render() {
return (
<SomeOtherComponent ref={this.targetRef}>some JSX to go here</SomeOtherComponent>
);
}
}
class SomeOtherComponent extends React.Component {
componentDidMount() {
}
render() {
return <div>some JSX to go here</div>;
}
}
Angular
import { Component, ElementRef, OnDestroy, ViewChild } from "@angular/core";
import {
disableBodyScroll,
enableBodyScroll,
clearAllBodyScrollLocks
} from "body-scroll-lock";
@Component({
selector: "app-scroll-block",
templateUrl: "./scroll-block.component.html",
styleUrls: ["./scroll-block.component.css"]
})
export class SomeComponent implements OnDestroy {
@ViewChild("scrollTarget") scrollTarget: ElementRef;
showTargetElement() {
disableBodyScroll(this.scrollTarget.nativeElement);
}
hideTargetElement() {
enableBodyScroll(this.scrollTarget.nativeElement);
}
ngOnDestroy() {
clearAllBodyScrollLocks();
}
}
Vanilla JS
In the html:
<head>
<script src="some-path-where-you-dump-the-javascript-libraries/lib/bodyScrollLock.js"></script>
</head>
Then in the javascript:
const targetElement = document.querySelector('#someElementId');
bodyScrollLock.disableBodyScroll(targetElement);
bodyScrollLock.enableBodyScroll(targetElement);
bodyScrollLock.clearAllBodyScrollLocks();
Demo
Check out the demo, powered by Vercel.
Functions
Function | Arguments | Return | Description |
---|
disableBodyScroll | targetElement: HTMLElement
options: BodyScrollOptions | void | Disables body scroll while enabling scroll on target element |
enableBodyScroll | targetElement: HTMLElement | void | Enables body scroll and removing listeners on target element |
clearAllBodyScrollLocks | null | void | Clears all scroll locks |
Options
reserveScrollBarGap
optional, default: false
If the overflow property of the body is set to hidden, the body widens by the width of the scrollbar. This produces an
unpleasant flickering effect, especially on websites with centered content. If the reserveScrollBarGap
option is set,
this gap is filled by a padding-right
on the body element. If disableBodyScroll
is called for the last target element,
or clearAllBodyScrollLocks
is called, the padding-right
is automatically reset to the previous value.
import { disableBodyScroll } from 'body-scroll-lock';
import type { BodyScrollOptions } from 'body-scroll-lock';
const options: BodyScrollOptions = {
reserveScrollBarGap: true,
};
disableBodyScroll(targetElement, options);
allowTouchMove
optional, default: undefined
To disable scrolling on iOS, disableBodyScroll
prevents touchmove
events.
However, there are cases where you have called disableBodyScroll
on an
element, but its children still require touchmove
events to function.
See below for 2 use cases:
Simple
disableBodyScroll(container, {
allowTouchMove: el => el.tagName === 'TEXTAREA',
});
More Complex
Javascript:
disableBodyScroll(container, {
allowTouchMove: el => {
while (el && el !== document.body) {
if (el.getAttribute('body-scroll-lock-ignore') !== null) {
return true;
}
el = el.parentElement;
}
},
});
Html:
<div id="container">
<div id="scrolling-map" body-scroll-lock-ignore>
...
</div>
</div>
References
https://medium.com/jsdownunder/locking-body-scroll-for-all-devices-22def9615177
https://stackoverflow.com/questions/41594997/ios-10-safari-prevent-scrolling-behind-a-fixed-overlay-and-maintain-scroll-posi
Changelog
Refer to the releases page.