or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-body-scroll-lock

Enables body scroll locking for iOS Mobile and Tablet, Android, desktop Safari/Chrome/Firefox without breaking scrolling of target elements like modals, lightboxes, flyouts, and nav-menus

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/body-scroll-lock@3.1.x

To install, run

npx @tessl/cli install tessl/npm-body-scroll-lock@3.1.0

index.mddocs/

Body Scroll Lock

Body Scroll Lock enables body scroll locking for iOS Mobile and Tablet, Android, and desktop browsers (Safari, Chrome, Firefox) without breaking scrolling of target elements like modals, lightboxes, flyouts, and navigation menus. It provides a robust cross-platform solution that addresses limitations of basic CSS overflow approaches.

Package Information

  • Package Name: body-scroll-lock
  • Package Type: npm
  • Language: JavaScript (with Flow type annotations)
  • Installation: npm install body-scroll-lock

Core Imports

import { disableBodyScroll, enableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';

For CommonJS:

const { disableBodyScroll, enableBodyScroll, clearAllBodyScrollLocks } = require('body-scroll-lock');

For browser/UMD:

<script src="lib/bodyScrollLock.js"></script>
<script>
  // Access via global: bodyScrollLock.disableBodyScroll, etc.
</script>

Basic Usage

import { disableBodyScroll, enableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';

// Get target element that should maintain scroll capability
const targetElement = document.querySelector('#modal-content');

// Show modal and disable body scroll
function showModal() {
  // ... show modal logic
  disableBodyScroll(targetElement);
}

// Hide modal and re-enable body scroll
function hideModal() {
  // ... hide modal logic
  enableBodyScroll(targetElement);
}

// Cleanup - useful for component unmounting
function cleanup() {
  clearAllBodyScrollLocks();
}

Architecture

Body Scroll Lock uses a platform-detection approach to provide optimal scroll locking:

  • iOS Detection: Uses touch event handling to prevent scrolling while preserving target element interactions
  • Desktop/Android: Uses CSS overflow: hidden with optional scrollbar gap reservation
  • Lock Management: Maintains internal registry of locked elements for proper cleanup
  • Touch Move Filtering: Provides customizable filtering for iOS touch events

Capabilities

Disable Body Scroll

Disables scrolling on the document body while preserving scroll functionality for a specified target element.

/**
 * Disables body scroll while enabling scroll on target element
 * @param targetElement - HTMLElement that should maintain scroll capability
 * @param options - Optional configuration for scroll lock behavior
 */
function disableBodyScroll(targetElement: HTMLElement, options?: BodyScrollOptions): void;

Parameters:

  • targetElement (HTMLElement): The element that should maintain scroll capability (e.g., modal content, flyout menu)
  • options (BodyScrollOptions, optional): Configuration options for scroll lock behavior

Usage Examples:

// Basic usage
const modal = document.querySelector('#modal');
disableBodyScroll(modal);

// With options
disableBodyScroll(modal, {
  reserveScrollBarGap: true,
  allowTouchMove: el => el.tagName === 'TEXTAREA'
});

Enable Body Scroll

Re-enables body scrolling and removes scroll lock for a specific target element.

/**
 * Enables body scroll and removes listeners on target element
 * @param targetElement - HTMLElement to remove scroll lock from
 */
function enableBodyScroll(targetElement: HTMLElement): void;

Parameters:

  • targetElement (HTMLElement): The element to remove scroll lock from (must match element used in disableBodyScroll)

Usage Examples:

// Re-enable scrolling for specific element
const modal = document.querySelector('#modal');
enableBodyScroll(modal);

Clear All Body Scroll Locks

Removes all scroll locks and restores normal body scrolling. Useful as a cleanup function.

/**
 * Clears all scroll locks and restores normal body scrolling
 */
function clearAllBodyScrollLocks(): void;

Usage Examples:

// Clear all locks (useful for cleanup)
clearAllBodyScrollLocks();

// Common pattern in React components
componentWillUnmount() {
  clearAllBodyScrollLocks();
}

Types

BodyScrollOptions

Configuration options for customizing scroll lock behavior.

interface BodyScrollOptions {
  /** 
   * Prevents layout shift by reserving scrollbar width as padding-right on body.
   * Avoids flickering effect when body width changes due to hidden scrollbar.
   */
  reserveScrollBarGap?: boolean;
  
  /** 
   * Function to determine if specific elements should allow touch move events on iOS.
   * Useful for nested scrollable elements within the target element.
   * @param el - Element being tested for touch move allowance
   * @returns true if element should allow touch move events
   */
  allowTouchMove?: (el: any) => boolean;
}

Usage Examples:

// Reserve scrollbar gap to prevent layout shift
disableBodyScroll(targetElement, {
  reserveScrollBarGap: true
});

// Allow touch move for specific elements
disableBodyScroll(targetElement, {
  allowTouchMove: el => el.tagName === 'TEXTAREA'
});

// Complex touch move filtering
disableBodyScroll(targetElement, {
  allowTouchMove: el => {
    // Allow scrolling for elements with special attribute
    while (el && el !== document.body) {
      if (el.getAttribute('body-scroll-lock-ignore') !== null) {
        return true;
      }
      el = el.parentElement;
    }
    return false;
  }
});

Platform-Specific Behavior

iOS Mobile and Tablet

  • Uses touch event handling (ontouchstart, ontouchmove)
  • Prevents document-level touchmove events with passive: false
  • Supports custom allowTouchMove filtering for nested scrollable elements
  • Maintains momentum scrolling (-webkit-overflow-scrolling: touch) on target elements

Android and Desktop

  • Uses CSS overflow: hidden on document body
  • Optional scrollbar gap reservation to prevent layout shifts
  • Automatic restoration of previous overflow settings

Cross-Platform Features

  • Nested Element Support: Multiple target elements can be locked simultaneously
  • Automatic Cleanup: Proper cleanup when last element is unlocked
  • Error Handling: Console warnings for missing target elements on iOS
  • Framework Agnostic: Works with vanilla JS, React, Angular, Vue, and other frameworks

Error Handling

The library includes built-in error handling:

  • Missing Target Element: Logs console error and returns early if targetElement is not provided on iOS
  • Duplicate Locks: Silently ignores attempts to lock the same element multiple times
  • Safe Cleanup: clearAllBodyScrollLocks() safely handles cleanup even if no locks are active

Common Use Cases

Modal/Dialog Management

// Modal component
class Modal {
  show() {
    this.element.style.display = 'block';
    disableBodyScroll(this.contentElement);
  }
  
  hide() {
    enableBodyScroll(this.contentElement);
    this.element.style.display = 'none';
  }
}

Navigation Menus

// Mobile navigation
const navToggle = document.querySelector('#nav-toggle');
const navMenu = document.querySelector('#nav-menu');

navToggle.addEventListener('click', () => {
  if (navMenu.classList.contains('open')) {
    navMenu.classList.remove('open');
    enableBodyScroll(navMenu);
  } else {
    navMenu.classList.add('open');
    disableBodyScroll(navMenu);
  }
});

React Integration

import React, { useEffect, useRef } from 'react';
import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock';

function Modal({ isOpen, children }) {
  const contentRef = useRef();
  
  useEffect(() => {
    if (isOpen && contentRef.current) {
      disableBodyScroll(contentRef.current);
    }
    
    return () => {
      if (contentRef.current) {
        enableBodyScroll(contentRef.current);
      }
    };
  }, [isOpen]);
  
  if (!isOpen) return null;
  
  return (
    <div className="modal-overlay">
      <div className="modal-content" ref={contentRef}>
        {children}
      </div>
    </div>
  );
}