body-scroll-lock
    DefinitelyTyped icon, indicating that this package has TypeScript declarations provided by the separate @types/body-scroll-lock package

    4.0.0-beta.0 • Public • Published

    Body scroll lock...just works with everything ;-)

    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:

    minzip 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
    // 1. Import the functions
    const bodyScrollLock = require('body-scroll-lock');
    const disableBodyScroll = bodyScrollLock.disableBodyScroll;
    const enableBodyScroll = bodyScrollLock.enableBodyScroll;
    
    // 2. Get a target element that you want to persist scrolling for (such as a modal/lightbox/flyout/nav).
    // Specifically, the target element is the one we would like to allow scroll on (NOT a parent of that element).
    // This is also the element to apply the CSS '-webkit-overflow-scrolling: touch;' if desired.
    const targetElement = document.querySelector('#someElementId');
    
    // 3. ...in some event handler after showing the target element...disable body scroll
    disableBodyScroll(targetElement);
    
    // 4. ...in some event handler after hiding the target element...
    enableBodyScroll(targetElement);
    React/ES6
    // 1. Import the functions
    import { disableBodyScroll, enableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';
    
    class SomeComponent extends React.Component {
      targetElement = null;
    
      componentDidMount() {
        // 2. Get a target element that you want to persist scrolling for (such as a modal/lightbox/flyout/nav).
        // Specifically, the target element is the one we would like to allow scroll on (NOT a parent of that element).
        // This is also the element to apply the CSS '-webkit-overflow-scrolling: touch;' if desired.
        this.targetElement = document.querySelector('#targetElementId');
      }
    
      showTargetElement = () => {
        // ... some logic to show target element
    
        // 3. Disable body scroll
        disableBodyScroll(this.targetElement);
      };
    
      hideTargetElement = () => {
        // ... some logic to hide target element
    
        // 4. Re-enable body scroll
        enableBodyScroll(this.targetElement);
      };
    
      componentWillUnmount() {
        // 5. Useful if we have called disableBodyScroll for multiple target elements,
        // and we just want a kill-switch to undo all that.
        // OR useful for if the `hideTargetElement()` function got circumvented eg. visitor
        // clicks a link which takes him/her to a different page within the app.
        clearAllBodyScrollLocks();
      }
    
      render() {
        return <div>some JSX to go here</div>;
      }
    }
    React/ES6 with Refs
    // 1. Import the functions
    import { disableBodyScroll, enableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';
    
    class SomeComponent extends React.Component {
      // 2. Initialise your ref and targetElement here
      targetRef = React.createRef();
      targetElement = null;
    
      componentDidMount() {
        // 3. Get a target element that you want to persist scrolling for (such as a modal/lightbox/flyout/nav).
        // Specifically, the target element is the one we would like to allow scroll on (NOT a parent of that element).
        // This is also the element to apply the CSS '-webkit-overflow-scrolling: touch;' if desired.
        this.targetElement = this.targetRef.current;
      }
    
      showTargetElement = () => {
        // ... some logic to show target element
    
        // 4. Disable body scroll
        disableBodyScroll(this.targetElement);
      };
    
      hideTargetElement = () => {
        // ... some logic to hide target element
    
        // 5. Re-enable body scroll
        enableBodyScroll(this.targetElement);
      };
    
      componentWillUnmount() {
        // 5. Useful if we have called disableBodyScroll for multiple target elements,
        // and we just want a kill-switch to undo all that.
        // OR useful for if the `hideTargetElement()` function got circumvented eg. visitor
        // clicks a link which takes him/her to a different page within the app.
        clearAllBodyScrollLocks();
      }
    
      render() {
        return (
          // 6. Pass your ref with the reference to the targetElement to SomeOtherComponent
          <SomeOtherComponent ref={this.targetRef}>some JSX to go here</SomeOtherComponent>
        );
      }
    }
    
    // 7. SomeOtherComponent needs to be a Class component to receive the ref (unless Hooks - https://reactjs.org/docs/hooks-faq.html#can-i-make-a-ref-to-a-function-component - are used).
    class SomeOtherComponent extends React.Component {
      componentDidMount() {
        // Your logic on mount goes here
      }
    
      // 8. BSL will be applied to div below in SomeOtherComponent and persist scrolling for the container
      render() {
        return <div>some JSX to go here</div>;
      }
    }
    Angular
    import { Component, ElementRef, OnDestroy, ViewChild } from "@angular/core";
    
    // 1. Import the functions
    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 {
      // 2. Get a target element that you want to persist scrolling for (such as a modal/lightbox/flyout/nav).
      // Specifically, the target element is the one we would like to allow scroll on (NOT a parent of that element).
      // This is also the element to apply the CSS '-webkit-overflow-scrolling: touch;' if desired.
      @ViewChild("scrollTarget") scrollTarget: ElementRef;
    
      showTargetElement() {
        // ... some logic to show target element
    
        // 3. Disable body scroll
        disableBodyScroll(this.scrollTarget.nativeElement);
      }
      
      hideTargetElement() {
        // ... some logic to hide target element
    
        // 4. Re-enable body scroll
        enableBodyScroll(this.scrollTarget.nativeElement);
      }
    
      ngOnDestroy() {
        // 5. Useful if we have called disableBodyScroll for multiple target elements,
        // and we just want a kill-switch to undo all that.
        // OR useful for if the `hideTargetElement()` function got circumvented eg. visitor
        // clicks a link which takes him/her to a different page within the app.
        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:

    // 1. Get a target element that you want to persist scrolling for (such as a modal/lightbox/flyout/nav).
    // Specifically, the target element is the one we would like to allow scroll on (NOT a parent of that element).
    // This is also the element to apply the CSS '-webkit-overflow-scrolling: touch;' if desired.
    const targetElement = document.querySelector('#someElementId');
    
    // 2. ...in some event handler after showing the target element...disable body scroll
    bodyScrollLock.disableBodyScroll(targetElement);
    
    // 3. ...in some event handler after hiding the target element...
    bodyScrollLock.enableBodyScroll(targetElement);
    
    // 4. Useful if we have called disableBodyScroll for multiple target elements,
    // and we just want a kill-switch to undo all that.
    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.

    Install

    npm i body-scroll-lock

    DownloadsWeekly Downloads

    354,198

    Version

    4.0.0-beta.0

    License

    MIT

    Unpacked Size

    46.3 kB

    Total Files

    9

    Last publish

    Collaborators

    • avatar