🔨 Cross-browser JavaScript library to disable scrolling page

Overview

scroll-lock

Cross-browser JavaScript library to disable scrolling page

Live demo | README на русском

New features 2.0

  • More advanced touch event handling algorithm
  • Horizontal scrolling support
  • Support for nested scrollable elements
  • Support for nested textarea and contenteditable
  • New API

Installation

Via npm or yarn

npm install scroll-lock
# or
yarn add scroll-lock
//es6 import
import { disablePageScroll, enablePageScroll } from 'scroll-lock';

//or
import scrollLock from 'scroll-lock';
scrollLock.disablePageScroll();
//...

//require
const scrollLock = require('scroll-lock');
scrollLock.disablePageScroll();
//...

Via script tag

<script src="path/to/scroll-lock.min.js"></script>
<script>
  scrollLock.disablePageScroll();
  //...
</script>

The es6 import will be used further in the examples, but these methods will also be available from the scrollLock object.

Disable page scrolling

When you call the disablePageScroll method, scrolling is also disabled in iOS and other touch devices (essence of the problem). But scrolling on touch devices will be disabled on all elements. To do this, you must explicitly specify which element will scroll on the page.

import { disablePageScroll, enablePageScroll } from 'scroll-lock';

//Get the element that should scroll when page scrolling is disabled
const $scrollableElement = document.querySelector('.my-scrollable-element');

//Pass the element to the argument and disable scrolling on the page
disablePageScroll($scrollableElement);

// Also, pass the element to the argument and enable scrolling on the page
enablePageScroll($scrollableElement);

Alternatively, you can specify the data-scroll-lock-scrollable attribute of the scrollable element.

<div class="my-scrollable-element" data-scroll-lock-scrollable></div>

Live demo: https://fl3nkey.github.io/scroll-lock/demos/index.html#ex-main

textarea and contenteditable

If a textarea or contenteditable is nested in a scrollable element, then do not worry, they will scroll without explicit indication.

Live demo: https://fl3nkey.github.io/scroll-lock/demos/index.html#ex-inputs

Filling the gap

When the disablePageScroll method is called, the scroll-lock indicates overflow: hidden; for body, thereby hiding the scroll bar. In some operating systems, the scroll bar has its physical width on the page, thus we get the effect of "displacement":



To prevent this, scroll-lock calculates the scroll bar width when calling the disablePageScroll method and fills in the space for the body element.



But this does not work for elements with fixed positioning. To do this, you must explicitly indicate which element needs to fill in the space.

import { addFillGapTarget, addFillGapSelector } from 'scroll-lock';

//selector
addFillGapSelector('.my-fill-gap-selector');

//element
const $fillGapElement = document.querySelector('.my-fill-gap-element');
addFillGapTarget($fillGapElement);

Or you can specify the data-scroll-lock-fill-gap attribute.

<div class="my-fill-gap-element" data-scroll-lock-fill-gap></div>

Live demo: https://fl3nkey.github.io/scroll-lock/demos/index.html#ex-fill-gap

Queue

A call to the disablePageScroll method creates a queue of calls. If you call the disablePageScroll method twice in a row, and then enablePageScroll, the page scrolling is not activated, because the enablePageScroll method will need to be called a second time.
If for some reason you need to activate scrolling the page out of turn, use the clearQueueScrollLocks method:

import { disablePageScroll, clearQueueScrollLocks } from 'scroll-lock';

disablePageScroll();
disablePageScroll();
disablePageScroll();
disablePageScroll();

enablePageScroll();
console.log(getScrollState()); //false

clearQueueScrollLocks();
enablePageScroll();
console.log(getScrollState()); //true

API

disablePageScroll(scrollableTarget)

Hides the scroll bar and disables page scrolling.

  • scrollableTarget - (HTMLElement | NodeList | HTMLElement array) scrollable element
import { disablePageScroll } from 'scroll-lock';

const $scrollableElement = document.querySelector('.my-scrollable-element');
disablePageScroll($scrollableElement);

enablePageScroll(scrollableTarget)

Shows the scroll bar and enables page scrolling.

  • scrollableTarget - (HTMLElement | NodeList | HTMLElement array) scrollable element
import { enablePageScroll } from 'scroll-lock';

const $scrollableElement = document.querySelector('.my-scrollable-element');
enablePageScroll($scrollableElement);

getScrollState()

Returns the state of the page scroll bar.

import { disablePageScroll, getScrollState } from 'scroll-lock';

console.log(getScrollState()); //true
disablePageScroll();
console.log(getScrollState()); //false

clearQueueScrollLocks()

Clears the queue value.

import { disablePageScroll, enablePageScroll, clearQueueScrollLocks, getScrollState } from 'scroll-lock';

disablePageScroll();
disablePageScroll();
disablePageScroll();
disablePageScroll();

enablePageScroll();
console.log(getScrollState()); //false

clearQueueScrollLocks();
enablePageScroll();
console.log(getScrollState()); //true

getPageScrollBarWidth(onlyExists)

Returns the width of the scroll bar.

  • onlyExists - (Boolean) only if scroll bar is exists
    Default value: false
import { getPageScrollBarWidth } from 'scroll-lock';

document.body.style.overflow = 'scroll';
console.log(getPageScrollBarWidth()); //Number
disablePageScroll();
console.log(getPageScrollBarWidth(true)); //Number
document.body.style.overflow = 'hidden';
console.log(getPageScrollBarWidth()); //Number
console.log(getPageScrollBarWidth(true)); //0

getCurrentPageScrollBarWidth()

Returns the width of the scroll bar to specific moment.

import { disablePageScroll, getCurrentPageScrollBarWidth } from 'scroll-lock';

console.log(getCurrentPageScrollBarWidth()); //Number
disablePageScroll();
console.log(getCurrentPageScrollBarWidth()); //0

addScrollableSelector(scrollableSelector)

Makes elements with this selector scrollable.

  • scrollableSelector - (String | String array) scrollable selector
    Initial value: ['[data-scroll-lock-scrollable]']
import { disablePageScroll, addScrollableSelector } from 'scroll-lock';

addScrollableSelector('.my-scrollable-selector');
disablePageScroll();

removeScrollableSelector(scrollableSelector)

Makes elements with this selector not scrollable.

  • scrollableSelector - (String | String array) scrollable selector
import { removeScrollableSelector } from 'scroll-lock';

removeScrollableSelector('.my-scrollable-selector');

addScrollableTarget(scrollableTarget)

Makes the element scrollable.

  • scrollableSelector - (HTMLElement | NodeList | HTMLElement array) scrollable element
import { disablePageScroll, addScrollableTarget } from 'scroll-lock';

const $scrollableElement = document.querySelector('.my-scrollable-element');
addScrollableTarget($scrollableElement);
disablePageScroll();

removeScrollableTarget(scrollableTarget)

Makes the element not scrollable.

  • scrollableSelector - (HTMLElement | NodeList | HTMLElement array) scrollable element
import { removeScrollableTarget } from 'scroll-lock';

const $scrollableElement = document.querySelector('.my-scrollable-element');
removeScrollableTarget($scrollableElement);

addLockableSelector(lockableSelector)

Makes elements with this selector lockable.

  • lockableSelector - (String | String array) lockable selector
    Initial value: ['[data-scroll-lock-lockable]']
import { disablePageScroll, addLockableSelector } from 'scroll-lock';

addLockableSelector('.my-lockable-selector');
disablePageScroll();

addLockableTarget(lockableTarget)

Makes the element lockable.

  • lockableTarget - (HTMLElement | NodeList | HTMLElement array) lockable element
import { disablePageScroll, addLockableTarget } from 'scroll-lock';

const $lockableElement = document.querySelector('.my-lockable-element');
addLockableTarget($lockableElement);
disablePageScroll();

addFillGapSelector(fillGapSelector)

Fills the gap with elements with this selector.

  • fillGapSelector - (String | String array) a fill gap selector
    Initial value: ['body', '[data-scroll-lock-fill-gap]']
import { addFillGapSelector } from 'scroll-lock';

addFillGapSelector('.my-fill-gap-selector');

removeFillGapSelector(fillGapSelector)

Returns the gap for elements with this selector.

  • fillGapSelector - (String | String array) a fill gap selector
import { removeFillGapSelector } from 'scroll-lock';

removeFillGapSelector('.my-fill-gap-selector');

addFillGapTarget(fillGapTarget)

Fills the gap at the element.

  • fillGapTarget - (HTMLElement | NodeList | HTMLElement array) a fill gap element
import { addFillGapTarget } from 'scroll-lock';

const $fillGapElement = document.querySelector('.my-fill-gap-element');
addScrollableTarget($fillGapElement);

removeFillGapTarget(fillGapTarget)

Returns the gap at the element.

  • fillGapTarget - (HTMLElement | NodeList | HTMLElement array) a fill gap element
import { removeFillGapTarget } from 'scroll-lock';

const $fillGapElement = document.querySelector('.my-fill-gap-element');
removeFillGapTarget($fillGapElement);

setFillGapMethod(fillGapMethod)

Changes the method of filling the gap.

  • fillGapMethod - (String: 'padding', 'margin', 'width', 'max-width', 'none') gap-filling method
    Default value: padding
import { setFillGapMethod } from 'scroll-lock';

setFillGapMethod('margin');

refillGaps()

Recalculates filled gaps.

import { refillGaps } from 'scroll-lock';

refillGaps();

See also


🙌 🙌 I would like to thank “Armani” for the translation. 🙌 🙌

Comments
  • No effect on iOS

    No effect on iOS

    This plugin is having no effect on iOS for me. I've installed the plugin and successfully called scrollLock.hide(), but on iOS there's no effect at all.

    My app is a Cordova app + Vue.js. When I run in the browser, body scrolling IS prevented, which is what I'd expect, but not on iOS.

    Are you aware of any issues with running under Cordova or Vue.js?

    enhancement 
    opened by p3v9d5ui 4
  • Should addFillGapSelector check if a selector is already present?

    Should addFillGapSelector check if a selector is already present?

    Thanks for the great library! It's a lifesaver and I've used it for multiple projects!

    I ran into an issue and was wondering if we could make a change to fix it.

    The Problem

    I'm building out a complex interface where the small screen and large screen experiences are very different. On large screens an element should have a fill gap selector. On small screens it should not. I had a function that looked roughly like this:

    updateScrollLock() {
       if(isLargeScreen) {
          scrollLock.addFillGapSelector('.foo');
       } else {
          scrollLock.removeFillGapSelector('.foo');
       }
    
       if(lockScrolling) { 
          scrollLock.disableScrolling();
       } else {
          scrollLock.enableScrolling();
       }
    }
    

    This function would be called whenever a user opened or closed a menu. This action could happen a number of times. At first the menu opening and closing was snappy, but over time I noticed that the performance degraded on large screen lower-power machines (and in low performing browsers).

    I dug into it and realized that every time the menu was toggled the fill gap selector was being re-added so that state.fillGapSelectors looked like this: ['.foo', '.foo', '.foo', ...]. I think this was slowing down performance, since when scroll locking was toggled it had to iterate over all those duplicate selectors and make changes.

    A Quick Fix

    I was able to work around it by doing something like this:

          scrollLock.addFillGapSelector('.foo');
    

    -->

          scrollLock.removeFillGapSelector('.foo');      
          scrollLock.addFillGapSelector('.foo');
    

    This works fine but it would be nice if it was impossible to get into this situation in the first place.

    A Fix in the Library

    We could avoid this by doing something like this:

    export const addFillGapSelector = (selector) => {
        if (selector) {
            const selectors = argumentAsArray(selector);
            selectors.map((selector) => {
                if(state.fillGapSelectors.indexOf(selector) !== -1) {
                    state.fillGapSelectors.push(selector);
                    if (!state.scroll) {
                        fillGapSelector(selector);
                    }
                }
            });
        }
    };
    

    I'd be happy to put up a PR if this makes sense to you.

    opened by Paul-Hebert 3
  • In scroll-enabled containers rangesliders do not work properly on touch devices

    In scroll-enabled containers rangesliders do not work properly on touch devices

    If a scroll-enabled container contains a range-slider <input type="range">, this slider is hard to use on a touch-device (because of the touch-event handling?)

    For an example see this demo page, that contains a regular slider and a slider in a scroll-enabled container (make sure you open it on a touch-device).

    bug 
    opened by vaupeh 2
  • many errors in console

    many errors in console

    Found a bug while using. When (for example, a menu) is scrolled to the maximum, but the user continues to drag his finger, then a large number of errors of the form start to appear in the console: "[Intervention] Ignored attempt to cancel a touchmove event with cancelable = false, for example because scrolling is in progress and cannot be interrupted." The solution is very simple by adding one condition before e.PreventDefault()

    if (e.cancelable) { e.preventDefault(); }

    opened by Aleksandr-JS-Developer 1
  • [Intervention] Ignored attempt to cancel a touchmove event

    [Intervention] Ignored attempt to cancel a touchmove event

    In some cases trying to cancel an event will fail with following error message: [Intervention] Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted.

    It happens because the event is not cancelable. To fix this, check for e.cancelable before e.preventDefault(); in the following two locations: https://github.com/FL3NKEY/scroll-lock/blob/master/src/scroll-lock.js#L440 https://github.com/FL3NKEY/scroll-lock/blob/master/src/scroll-lock.js#L447

    if(e.cancelable) {
        e.preventDefault();
    }
    

    image

    opened by raphaelportmann 1
  • Prevent duplicate scroll gap selectors being added

    Prevent duplicate scroll gap selectors being added

    It's possible to add the same selector multiple times. This can lead to perf slowdowns as DOM operations run on each selector By preventing duplicate selectors we can avoid this

    This commit also adds a test to confirm this works.

    Fixes #13

    opened by Paul-Hebert 1
  • Feature/API request

    Feature/API request

    Could you add a method, eg. AddLockableTarget() ? That way in addition to locking body, we could also lock scrollable divs.

    eg.

    <body {{LOCKHERE}}>
       <div {{LOCKHERE}}>
          <div {{SCROLLHERE}}>
    
          </div>
       <div>
    <body>
    
    enhancement good first issue 
    opened by danDanV1 1
  • 2.1

    2.1

    • Fix bug of fills gap when overflow is hidden
    • Fix bug with queue state (could be less than 0)
    • Add lockable feature
    • More advanced getPageScrollBarWidth method
    • Add demo with Modal
    • Add demo with Sidebar
    opened by FL3NKEY 0
  • scroll-lock not working in shadow components

    scroll-lock not working in shadow components

    Is this library supposed to work when being invoked on elements running in shadowed components?

    Seems as though the whole screen gets locked if the element being made scrollable is inside of a shadowed component...?

    opened by neilkyoung 0
  • safari ios 13.5, body is not locked

    safari ios 13.5, body is not locked

    As the title says, body lock works everywhere just fine but safari on ios. How to deal with safari on ios devices? The only thing i found could work is to set additionaly body to position fixed but it breaks scroll position after.

    opened by n19htz 4
  • Normal Apple pencil events partially blocked

    Normal Apple pencil events partially blocked

    I'm sorry for the way I'm going to phrase things, but my knowledge is a bit limited in this area still (regarding JS events).

    All of this is done on an iPad with an Apple Pencil.

    I have a URL with a custom onclick event. The thing is, when I call disablePageScroll(), then the click event doesn't fire anymore (I am pretty sure it's the click event, I didn't check though, but my anonymous function in onclick does not get called). However, the link does change color, so clearly some events are still getting through.

    When the page scroll is enabled, not only does the URL change color, but also the whole rectangular area briefly flashes to a black. I made a GIF of it.

    I first click on "share", which flashes red (page scroll is disabled by default), then I enable page scroll by clicking on "on" (an alert shows that page scroll is enabled). I then click on "share" again and then you see a red flash and a black rectangular flash, indicating that disablePageScroll() is blocking some event.

    output

    Finding the culprit was simple, but I don't have any clue on what unintended side effects I get when I comment it out, but for my purposes, I did for now.

    image

    opened by melvinroest 2
  • Implicit Root Scroller On Chrome 73

    Implicit Root Scroller On Chrome 73

    https://www.chromestatus.com/feature/5162094739587072 This update is causing displacement of modals with input fields when the field becomes focused. I tested it with this example https://fl3nkey.github.io/scroll-lock/demos/sidebar/sidebar.html on android chrome 75. Steps to reproduce:

    • open modal
    • click on the name field
    • modal scrolls to the bottom
    opened by isBatak 0
Owner
null
❌ Disable spam emails sent by the Claremont Colleges.

Claremont Spam Disabler Have you ever had the misfortune of seeing an email like this? Most likely not, unless you at one point attended a college in

Radian LLC 3 Jun 5, 2022
Bookmarklet exploit that can force-disable extensions installed on Chrome. Also has a very fancy GUI to manage all extensions!

ext remover Bookmarklet exploit that can force-disable any extension installed on Google Chrome Instructions Here are the instructions to using this e

Echo 124 Jan 6, 2023
It's a tiny freeware library on TypeScript to sinhronize page scrolling and navigation meny-bar.

Scroll progress (dual-side-scroll) v1.2.3 Assignment This tiny plugin is designed to show the progress of the page scrolling interactively. There are

Evgeny 4 May 17, 2022
Seamless and lightweight parallax scrolling library implemented in pure JavaScript utilizing Hardware acceleration for extra performance.

parallax-vanilla.js Seamless and lightweight parallax scrolling library implemented in pure JavaScript utilizing Hardware acceleration for extra perfo

Erik Engervall 91 Dec 16, 2022
The awesomebooks project is a simple list, but separated into 3 parts and given a retro feel. The main page is where we can add books, and on another page we can see the list, and remove items. There is also a "contact-us" page.

Awesome Books This is the restructured version of the famous awesome-books project! Here you can find JavaScript broken into modules, using import-exp

Matt Gombos 12 Nov 15, 2022
Modern Cross Browser Testing in JavaScript using Playwright

Modern Cross Browser Testing in JavaScript using Playwright This repository contains the example code for the Modern Cross Browser Testing in JavaScri

Applitools 5 Oct 3, 2022
A lightweight cross browser javascript scrollbar.

tinyscrollbar ** HELP MAINTAINER NEEDED!! ** Environments in which to use tinyscrollbar Browser support differs between the jQuery plugin and the plai

Maarten Baijs 398 Nov 9, 2022
Simple wrapper for cross-browser usage of the JavaScript Fullscreen API

screenfull Simple wrapper for cross-browser usage of the JavaScript Fullscreen API, which lets you bring the page or any element into fullscreen. Smoo

Sindre Sorhus 6.7k Dec 30, 2022
Make the content slide prettily across the screen with variable sizes of scrolling items, in any of four directions, pausing while the mouse is over the marquee, and all with vanilla JavaScript.

TEG Marquee Make the content slide prettily across the screen with variable sizes of scrolling items, in any of four directions, pausing while the mou

Paul B. Joiner 0 Dec 30, 2021
A simple smooth scrolling using 100% vanilla JavaScript.

SmoothScroll.js A simple smooth scrolling using 100% vanilla JavaScript, and it's only 3kb! > Demo Usage // index.html <html> <head> <link rel="

Ray Chang 5 Aug 24, 2022
Vanilla javascript (ES6) function enabling drag scrolling on desktop

Drag-n-scroll also on desktop devices, by LCweb No dependencies vanilla javascript function to easily implement a nice drag-to-scroll effect using dse

Luca 2 Apr 5, 2022
dotdotdot.js, advanced cross-browser ellipsis for multiple line content.

dotdotdot Dotdotdot is a javascript plugin for truncating multiple line content on a webpage. It uses an ellipsis to indicate that there is more text

Fred Heusschen 1.7k Dec 20, 2022
🦎 The cross browser extension that blends in

Rango Rango is a cross browser extension that helps you interact with web pages using your voice and talon. It does this by drawing hints with letters

David Tejada 37 Jan 3, 2023
A cross-platform browser extension that changes the way seasons are display on Crunchyroll.

Crunchyroll With Better Seasons Crunchyroll With Better Seasons is a cross-platform browser extension that changes the way seasons are displayed on Cr

null 9 Nov 4, 2022
Pretty, customisable, cross browser replacement scrollbars

jScrollPane - cross browser custom scroll bars jScrollPane is a jQuery plugin which allows you to replace a browser's default scroll bars (on an eleme

Kelvin Luck 2.2k Dec 15, 2022
A jQuery plugin that adds cross-browser mouse wheel support.

jQuery Mouse Wheel Plugin A jQuery plugin that adds cross-browser mouse wheel support with delta normalization. In order to use the plugin, simply bin

jQuery 3.9k Dec 26, 2022
Freewall is a cross-browser and responsive jQuery plugin to help you create grid, image and masonry layouts for desktop, mobile, and tablet...

Freewall Freewall is a cross-browser and responsive jQuery plugin to help you create many types of grid layouts: flexible layouts, images layouts, nes

Minh Nguyen 1.9k Dec 27, 2022
A super-lightweight, highly configurable, cross-browser date / time picker jQuery plugin

Zebra Datepicker A super-lightweight, highly configurable, cross-browser date/time picker jQuery plugin Zebra_Datepicker is a small yet and highly con

Stefan Gabos 391 Dec 29, 2022
Cross-browser plugin to remove addictive features on YouTube like thumbnails, comments, previews and more...

ZenTube Installation Features Remove some (more) elements from Youtube to make it less addictive. Mix and match between the following options: Hide or

inversepolarity 57 Dec 17, 2022