scrollLock

scrollLock

Prevent page scrolling while dragging

The scrollLock plugin prevents page or container scrolling during drag operations. Essential for mobile devices and touch interfaces where dragging triggers unwanted scrolling.

scrollLock(); // Lock all scrolling
scrollLock({ lockAxis: 'x' }); // Only horizontal scrolling
scrollLock({ container: myContainer }); // Lock specific container
scrollLock({ allowScrollbar: true }); // Keep scrollbars clickable

Basic Usage

import { createSignal } from 'solid-js';
import { scrollLock, useDraggable } from '@neodrag/solid';

function ScrollLockExamples() {
  const [fullLock, setFullLock] = createSignal<HTMLElement | null>(
    null,
  );
  const [horizontal, setHorizontal] =
    createSignal<HTMLElement | null>(null);
  const [container, setContainer] = createSignal<HTMLElement | null>(
    null,
  );
  const [scrollArea, setScrollArea] =
    createSignal<HTMLElement | null>(null);

  useDraggable(fullLock, [scrollLock()]);
  useDraggable(horizontal, [scrollLock({ lockAxis: 'x' })]);
  useDraggable(container, [
    scrollLock({ container: () => scrollArea()! }),
  ]);

  return (
    <div>
      <div ref={setFullLock}>Drag me - page won't scroll</div>
      <div ref={setHorizontal}>Vertical scrolling still works</div>

      <div ref={setScrollArea} class="scroll-area">
        <div ref={setContainer}>Lock specific container</div>
      </div>
    </div>
  );
}

Dynamic Configuration

import { createSignal } from 'solid-js';
import {
  scrollLock,
  useDraggable,
  createCompartment,
} from '@neodrag/solid';

function DynamicScrollLock() {
  const [element, setElement] = createSignal<HTMLElement | null>(
    null,
  );
  const [targetContainer, setTargetContainer] =
    createSignal<HTMLElement | null>(null);
  const [lockMode, setLockMode] = createSignal<
    'both' | 'x' | 'y' | 'none'
  >('both');
  const [allowScrollbar, setAllowScrollbar] = createSignal(false);

  const scrollLockComp = createCompartment(() => {
    if (lockMode() === 'none') return null;

    return scrollLock({
      lockAxis: lockMode(),
      allowScrollbar: allowScrollbar(),
      container: () => targetContainer()!,
    });
  });

  useDraggable(element, [scrollLockComp]);

  return (
    <div>
      <div ref={setTargetContainer} class="scroll-container">
        <div ref={setElement}>
          Lock Mode: {lockMode()}
          <br />
          Scrollbar: {allowScrollbar() ? 'visible' : 'hidden'}
        </div>
      </div>

      <div class="controls">
        <label>
          Lock Mode:
          <select
            value={lockMode()}
            onChange={(e) => setLockMode(e.target.value as any)}
          >
            <option value="both">Both Axes</option>
            <option value="x">X-Axis Only</option>
            <option value="y">Y-Axis Only</option>
            <option value="none">Disabled</option>
          </select>
        </label>

        <label>
          <input
            type="checkbox"
            checked={allowScrollbar()}
            onChange={(e) => setAllowScrollbar(e.target.checked)}
          />
          Allow Scrollbar
        </label>
      </div>
    </div>
  );
}

Common Use Cases

Mobile-Friendly Dragging

// Prevent mobile bounce scrolling
scrollLock({
  lockAxis: 'both',
  allowScrollbar: false, // Hide scrollbars on mobile
});

Modal/Overlay Dragging

// Lock body scrolling when dragging modal
scrollLock({
  container: document.body,
  lockAxis: 'both',
});

Horizontal Slider

// Allow vertical scrolling, prevent horizontal
scrollLock({
  lockAxis: 'x',
  allowScrollbar: true, // Keep vertical scrollbar
});

Canvas/Editor Tools

// Lock scrolling within canvas container
scrollLock({
  container: () => document.getElementById('canvas-container'),
  lockAxis: 'both',
});

Combining with Other Plugins

With Bounds

const plugins = [scrollLock(), bounds(BoundsFrom.viewport())];

Touch-Optimized Setup

const plugins = [
  scrollLock({ lockAxis: 'both' }),
  threshold({ distance: 5, delay: 100 }), // Prevent accidental drags
  touchAction('none'), // Disable browser touch behaviors
];

Conditional ScrollLock

// Only lock on touch devices
const isTouchDevice = 'ontouchstart' in window;
const plugins = [
  isTouchDevice ? scrollLock() : null,
  bounds(BoundsFrom.parent()),
].filter(Boolean);

Platform Considerations

iOS Safari

// iOS requires specific handling
scrollLock({
  lockAxis: 'both',
  allowScrollbar: false, // Important for iOS
  container: document.body,
});

Desktop vs Mobile

const isMobile = /Mobi|Android/i.test(navigator.userAgent);

scrollLock({
  lockAxis: isMobile ? 'both' : 'y', // More aggressive on mobile
  allowScrollbar: !isMobile, // Keep scrollbars on desktop
});

How It Works

The scrollLock plugin prevents scrolling by temporarily modifying CSS properties:

During drag start:

  1. Stores original styles - user-select, touch-action, overflow
  2. Applies lock styles:
    • user-select: none - Prevents text selection
    • overflow: hidden - Hides scrollbars (if allowScrollbar: false)
    • touch-action: pan-x/pan-y/none - Controls touch scrolling direction

During drag end:

  1. Restores original styles - Everything returns to normal

Target selection:

  • If container specified, applies to that element
  • If container is document.documentElement or not specified, applies to document.body
  • Function containers are called dynamically each time

API Reference

function scrollLock(
  options?: {
    lockAxis?: 'x' | 'y' | 'both';
    container?: HTMLElement | (() => HTMLElement);
    allowScrollbar?: boolean;
  } | null,
): Plugin;

Options:

  • lockAxis - Which scrolling to prevent ('both' default)
    • 'x' - Only horizontal scrolling locked
    • 'y' - Only vertical scrolling locked
    • 'both' - All scrolling locked
  • container - Where to apply scroll lock (document.documentElement default)
    • HTMLElement - Specific element
    • () => HTMLElement - Dynamic element selection
  • allowScrollbar - Keep scrollbars clickable (false default)
    • true - Scrollbars remain visible and functional
    • false - Scrollbars hidden via overflow: hidden

Returns: A plugin object for use with draggable.

Browser Support: All modern browsers, with special handling for iOS Safari touch behaviors.