221

I'm trying to disable the html/body scrollbar of the parent while I'm using a lightbox. The main word here is disable. I do not want to hide it with overflow: hidden;.

The reason for this is that overflow: hidden makes the site jump and take up the area where the scroll was.

I want to know if its possible to disable a scrollbar while still showing it.

AJNeufeld
  • 8,231
  • 1
  • 22
  • 40
Dejan.S
  • 16,281
  • 21
  • 64
  • 104
  • 1
    Should any scrolling be possible? (Does the lightbox have any scrollbars?) – Šime Vidas Jan 02 '12 at 14:09
  • the site got scrolling but i just want to disable it while the lightbox is open. i just want to know if its possible to disable a scrollbar while showing it. nothing else is needed like how to do it in a lightbox or anything else. – Dejan.S Jan 02 '12 at 14:10
  • what is the problem using lightbox with scrollbar? – manny Jan 02 '12 at 14:11
  • @Dejan OK, but does the lightbox itself contain any scrollbars? – Šime Vidas Jan 02 '12 at 14:11
  • yes, but why is that important? – Dejan.S Jan 02 '12 at 14:11
  • 1
    @Dejan Because I know how to disable *all* scrolling - that would disable the main scrollbar, but also the one in the lightbox... – Šime Vidas Jan 02 '12 at 14:13
  • @manny when i scroll the lightbox and it ends it starts to scroll the background, and even if the lightbox dont got scrolls i dont want the user to be able to scroll the background from the lightbox. – Dejan.S Jan 02 '12 at 14:13
  • @Sime Vidas.. but does it disable and still show it? let me know how to – Dejan.S Jan 02 '12 at 14:14
  • @Dejan.S See here: http://stackoverflow.com/a/4770179/425275 It explains how to disable mouse scrolling altogether. – Šime Vidas Jan 02 '12 at 14:32
  • @ŠimeVidas that works when scrolling outside the lightbox but when scrolling inside it still scrolls the background. but it can work with some modifications. – Dejan.S Jan 02 '12 at 14:53
  • With this solution; page is scrolling to top of the page when disabled body. – MERT DOĞAN Feb 29 '16 at 13:32
  • 1
    Please do not edit the answer back into this post. The answer section below is for answers. If you have an answer to your question that is _different_ from an answer below, you may add your own solution in the answer selection. – intcreator Jul 21 '16 at 21:03

21 Answers21

197

If the page under the overlayer can be "fixed" at the top, when you open the overlay you can set

body { position: fixed; overflow-y:scroll }

you should still see the right scrollbar but the content is not scrollable. When you close the overlay just revert these properties with

body { position: static; overflow-y:auto }

I just proposed this way only because you wouldn't need to change any scroll event

Update

You could also do a slight improvement: if you get the document.documentElement.scrollTop property via javascript just before the layer opening, you could dynamically assign that value as top property of the body element: with this approach the page will stand in its place, no matter if you're on top or if you have already scrolled.

Css

.noscroll { position: fixed; overflow-y:scroll }

JS

$('body').css('top', -(document.documentElement.scrollTop) + 'px')
         .addClass('noscroll');
Fabrizio Calderan loves trees
  • 109,094
  • 24
  • 154
  • 160
  • 2
    it messes with my layout but with some modifications it just might work. i like this i will try it out more – Dejan.S Jan 02 '12 at 15:04
  • 5
    with a little modification this is works. i added a width: 100%; im gone update my question with the solution but you can modify your with with the width: 100%;? thanks for the suggestion – Dejan.S Jan 02 '12 at 15:13
  • It adds/appends a new scroll (vertical) in my application :( Yet i want my default browser scroll(y) to be disabled for a moment of time. – ram Sep 17 '13 at 13:25
  • 3
    I don’t know why, but this works better for me: `$('body').css('top', -($('body').scrollTop()) + 'px').addClass('noscroll');` – PDXIII Oct 16 '14 at 11:40
  • Working fine with FF and Chrome. But this is not working with IE. Do I need to write IE specific additional styles for this to work in IE ? – Awesome Nov 07 '14 at 06:01
  • Maybe its other code I have on my site that interferes but on Chrome 43 this is not working . It jumps to the top anyway even with your JS hack. – Dustin Poissant May 15 '15 at 20:45
  • 4
    @whitesiroi, you are right. I've updated the fiddle: https://jsfiddle.net/evpozdniakov/2m8km9wg/ Thank you! – evpozdniakov Nov 30 '15 at 16:39
  • 1
    For those reading the comments for a solution, the last fiddle provides a solution for disabling and re-enabling the scrollbar whilst retaining the scrollbar position in both cases. Thanks! – user1063287 Jan 31 '16 at 12:46
  • 1
    It hides the background scroller. I want that background scroller doesn't hide and a user can manually scrolls the body. – legend Jan 06 '17 at 09:35
  • @legend add "overflow-y:scroll" to the body.scrolDisabled CSS in the last fiddle and it should work – RustyH Sep 07 '18 at 14:25
  • This answer might help you. https://stackoverflow.com/questions/30447487/html-page-scrolls-on-keydown – Abhijeet Giram Aug 11 '20 at 13:04
89

Four little additions to the accepted solution:

  1. Apply 'noscroll' to html instead of to body to prevent double scroll bars in IE
  2. To check if there's actually a scroll bar before adding the 'noscroll' class. Otherwise, the site will also jump pushed by the new non-scrolling scroll bar.
  3. To keep any possible scrollTop so the entire page doesn't go back to the top (like Fabrizio's update, but you need to grab the value before adding the 'noscroll' class)
  4. Not all browsers handle scrollTop the same way as documented at http://help.dottoro.com/ljnvjiow.php

Complete solution that seems to work for most browsers:

CSS

html.noscroll {
    position: fixed; 
    overflow-y: scroll;
    width: 100%;
}

Disable scroll

if ($(document).height() > $(window).height()) {
     var scrollTop = ($('html').scrollTop()) ? $('html').scrollTop() : $('body').scrollTop(); // Works for Chrome, Firefox, IE...
     $('html').addClass('noscroll').css('top',-scrollTop);         
}

Enable scroll

var scrollTop = parseInt($('html').css('top'));
$('html').removeClass('noscroll');
$('html,body').scrollTop(-scrollTop);

Thanks to Fabrizio and Dejan for putting me on the right track and to Brodingo for the solution to the double scroll bar

isherwood
  • 46,000
  • 15
  • 100
  • 132
Mike Garcia
  • 1,801
  • 12
  • 12
  • 1
    This tested well on desktop but on the iPhone it created a blank white screen until the reader attempted to scroll the page. Once the reader tried to interact with the page, the intended screen became visible with the scroll locked. – Michael Khalili Jun 07 '13 at 22:01
  • This worked perfectly with Fancybox on iPad to disable scrolling of lightbox image with `afterLoad()` and `afterClose` callbacks. Could be useful for others searching this question. – Tomanow Aug 14 '13 at 18:47
  • In the "enable scroll" part, you might want to remove the style attribute that is placed on the html element after a call to "disable scroll"; don't you ? – Ben Aug 20 '13 at 09:52
  • 3
    jQuery standardises native DOM's `scrollTop` such that line 2 of the 'Disable scroll' code can be expressed as `$( window ).scrollTop()`. – Barney Sep 13 '13 at 11:45
  • I think one other thing needs to be added to this. On Chrome in OSX, it's possible for the browser to "over-scroll", giving scrolling an elastic feel. With this code if you are in the grey area above or below the page and you hover over a spot where you disable scrolling. It will stick to a spot above/below the browser window. For this reason you need to add a check for a negative scroll value: `if (scrollTop < 0) { scrollTop = 0; }`. Here is the full code for disable https://gist.github.com/jayd3e/2eefbbc571cd1668544b. – JayD3e Aug 19 '14 at 00:56
  • The position fixed hack (suggested in most answers here) produces terrible UX. Body be jumpin' all over the place. This is by far the best solution. – egr103 Feb 08 '15 at 18:02
  • The "Enable scroll" part is jumping slightly up and down when having a scrollTop > 0. This happens on Chrome, while Firefox handles it smoothly (both on Mac OS). – Patrick Rudolph Mar 23 '15 at 08:37
  • This is best solution i ever find. I tried a lot of techniques but nothing work. This is the working best... – MERT DOĞAN Feb 29 '16 at 13:43
  • Second line of "Enable scroll" should be $('html').removeClass('noscroll').css('top',''); For me this solved a lot of problems with a responsive menu – Malasorte May 05 '16 at 04:15
  • @PatrickRudolph I think this happens because of the vertical scrollbar, which dissapears on Chrome. The wider layout then has different line-breaks which is the reason for the jump. Forcing scrollbars with `overflow-y: scroll;` fixed it for me. – AAGD Dec 15 '19 at 17:56
35

With jQuery inluded:


disable

$.fn.disableScroll = function() {
    window.oldScrollPos = $(window).scrollTop();

    $(window).on('scroll.scrolldisabler',function ( event ) {
       $(window).scrollTop( window.oldScrollPos );
       event.preventDefault();
    });
};

enable

$.fn.enableScroll = function() {
    $(window).off('scroll.scrolldisabler');
};

usage

//disable
$("#selector").disableScroll();
//enable
$("#selector").enableScroll();
ceed
  • 639
  • 1
  • 6
  • 14
  • 1
    this was perfect for me, the previous answer made my page bounce, this one doesn't – Bogdan Tomi Jun 07 '16 at 09:12
  • glad i could help, you might need to check for "touchmove" too, for mobile devices, cheers – ceed Jun 07 '16 at 22:00
  • Well, this *almost* worked for me. It worked wonders on Windows (Firefox, Chrome) and MacOS in Firefox... but then I went to Windows IE and Edge, along with MacOS in Chrome and Safari. The problem was no longer about the page jumping from side-to-side, but the scrollbar was jumping from top to current position or the page was flickering up top. Man, this was *so*, *so* close. Maybe we can still make it work. – Steven Ventimiglia Mar 29 '17 at 19:05
  • This worked perfectly for me, thank you ! Ideal tu use on a website where the overlay menu is also at the bottom of the page ! – SauriolJf Aug 27 '17 at 15:32
18

I'm the OP

With the help of answer from fcalderan I was able to form a solution. I leave my solution here as it brings clarity to how to use it, and adds a very crucial detail, width: 100%;

I add this class

body.noscroll
{
    position: fixed; 
    overflow-y: scroll;
    width: 100%;
}

this worked for me and I was using Fancyapp.

ksav
  • 13,381
  • 5
  • 27
  • 51
Dejan.S
  • 16,281
  • 21
  • 64
  • 104
12

This worked really well for me....

// disable scrolling
$('body').bind('mousewheel touchmove', lockScroll);

// enable scrolling
$('body').unbind('mousewheel touchmove', lockScroll);


// lock window scrolling
function lockScroll(e) {
    e.preventDefault();
}

just wrap those two lines of code with whatever decides when you are going to lock scrolling.

e.g.

$('button').on('click', function() {
     $('body').bind('mousewheel touchmove', lockScroll);
});
dmackenz
  • 129
  • 1
  • 2
  • This results in an error in Chrome 87: `[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/6662647093133312` ([ref](https://www.chromestatus.com/features/6662647093133312)) – isherwood Nov 29 '20 at 18:57
7

You cannot disable the scroll event, but you can disable the related actions that lead to a scroll, like mousewheel and touchmove:

$('body').on('mousewheel touchmove', function(e) {
      e.preventDefault();
});
tocqueville
  • 4,111
  • 1
  • 34
  • 48
5

You can hide the body's scrollbar with overflow: hidden and set a margin at the same time so that the content doesn't jump:

let marginRightPx = 0;
if(window.getComputedStyle) {
    let bodyStyle = window.getComputedStyle(document.body);
    if(bodyStyle) {
        marginRightPx = parseInt(bodyStyle.marginRight, 10);
    }
}

let scrollbarWidthPx = window.innerWidth - document.body.clientWidth;
Object.assign(document.body.style, {
    overflow: 'hidden',
    marginRight: `${marginRightPx + scrollbarWidthPx}px`
});

And then you can add a disabled scrollbar to the page to fill in the gap:

textarea {
  overflow-y: scroll;
  overflow-x: hidden;
  width: 11px;
  outline: none;
  resize: none;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  border: 0;
}
<textarea></textarea>

I did exactly this for my own lightbox implementation. Seems to be working well so far.

mpen
  • 237,624
  • 230
  • 766
  • 1,119
  • 1
    I actually like this method the best. Very simple and works great. Thanks! – dericcain May 10 '17 at 13:37
  • 1
    Very simple but it comes with one caveat. This won't work when the browser renders the scrollbar on the left (RTL). Unfortunately, there is no simple way to detect where the browser puts the scrollbar. See https://stackoverflow.com/q/52404285/2370007 – Philipp Mitterer Jan 29 '21 at 15:34
2

This is the solution we went with. Simply save the scroll position when the overlay is opened, scroll back to the saved position any time the user attempted to scroll the page, and turn the listener off when the overlay is closed.

It's a bit jumpy on IE, but works like a charm on Firefox/Chrome.

var body = $("body"),
  overlay = $("#overlay"),
  overlayShown = false,
  overlayScrollListener = null,
  overlaySavedScrollTop = 0,
  overlaySavedScrollLeft = 0;

function showOverlay() {
  overlayShown = true;

  // Show overlay
  overlay.addClass("overlay-shown");

  // Save scroll position
  overlaySavedScrollTop = body.scrollTop();
  overlaySavedScrollLeft = body.scrollLeft();

  // Listen for scroll event
  overlayScrollListener = body.scroll(function() {
    // Scroll back to saved position
    body.scrollTop(overlaySavedScrollTop);
    body.scrollLeft(overlaySavedScrollLeft);
  });
}

function hideOverlay() {
  overlayShown = false;

  // Hide overlay
  overlay.removeClass("overlay-shown");

  // Turn scroll listener off
  if (overlayScrollListener) {
    overlayScrollListener.off();
    overlayScrollListener = null;
  }
}

// Click toggles overlay
$(window).click(function() {
  if (!overlayShown) {
    showOverlay();
  } else {
    hideOverlay();
  }
});
/* Required */
html, body { margin: 0; padding: 0; height: 100%; background: #fff; }
html { overflow: hidden; }
body { overflow-y: scroll; }

/* Just for looks */
.spacer { height: 300%; background: orange; background: linear-gradient(#ff0, #f0f); }
.overlay { position: fixed; top: 20px; bottom: 20px; left: 20px; right: 20px; z-index: -1; background: #fff; box-shadow: 0 0 5px rgba(0, 0, 0, .3); overflow: auto; }
.overlay .spacer { background: linear-gradient(#88f, #0ff); }
.overlay-shown { z-index: 1; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<h1>Top of page</h1>
<p>Click to toggle overlay. (This is only scrollable when overlay is <em>not</em> open.)</p>
<div class="spacer"></div>
<h1>Bottom of page</h1>
<div id="overlay" class="overlay">
  <h1>Top of overlay</h1>
  <p>Click to toggle overlay. (Containing page is no longer scrollable, but this is.)</p>
  <div class="spacer"></div>
  <h1>Bottom of overlay</h1>
</div>
0b10011
  • 17,120
  • 3
  • 62
  • 80
  • Works indeed, but taints the design in case of a fixed header, as the scrollbar dives under it. – Leon Willens Mar 14 '19 at 15:21
  • @LeonWillens I'm not sure what you mean. If you're concerned about the overlay appearing under a fixed header, you'll need to adjust the `z-index` of `.overlay-shown` to be greater than the fixed header. (I have this code running in production on a site with a fixed header and it works fine.) – 0b10011 Mar 14 '19 at 16:46
  • No, I was talking about the scrollbar of the `body` tag. I experimented with your code yesterday and I have a fixed header at the top of my page. If I handle scrolling via the `body` tag, the scrollbar will be under the fixed header. – Leon Willens Mar 15 '19 at 09:13
  • 1
    @LeonWillens Oops, looks like I fixed this in production at some point and forgot to update this. It looks like instead of `$("body")`, we're using `$(window)`. And instead of `z-index: -1` on the overlay to hide it, you can use `visibility: hidden` or similar. See https://jsfiddle.net/8p2gfeha/ for a quick test to see if it works. Fixed header no longer appears on top of the scrollbar. I'll eventually try to incorporate these changes into the answer. – 0b10011 Mar 15 '19 at 14:44
  • Works like a charm! Thank you :) – Leon Willens Mar 18 '19 at 08:46
1

I like to stick to the "overflow: hidden" method and just add padding-right that's equal to the scrollbar width.

Get scrollbar width function, by lostsource.

function getScrollbarWidth() {
    var outer = document.createElement("div");
    outer.style.visibility = "hidden";
    outer.style.width = "100px";
    outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps

    document.body.appendChild(outer);

    var widthNoScroll = outer.offsetWidth;
    // force scrollbars
    outer.style.overflow = "scroll";

    // add innerdiv
    var inner = document.createElement("div");
    inner.style.width = "100%";
    outer.appendChild(inner);        

    var widthWithScroll = inner.offsetWidth;

    // remove divs
    outer.parentNode.removeChild(outer);

    return widthNoScroll - widthWithScroll;
}

When showing the overlay, add "noscroll" class to html and add padding-right to body:

$(html).addClass("noscroll");
$(body).css("paddingRight", getScrollbarWidth() + "px");

When hiding, remove the class and padding:

$(html).removeClass("noscroll");
$(body).css("paddingRight", 0);

The noscroll style is just this:

.noscroll { overflow: hidden; }

Note that if you have any elements with position:fixed you need to add the padding to those elements too.

Community
  • 1
  • 1
Janne Annala
  • 15,368
  • 7
  • 23
  • 33
  • I used this solution. It helped me to avoid double scrollbars (one 'blank' for the body and a second one for the content of the overlay). It also works very well on OSX which might or might not show scrollbars at all. – Novazembla Apr 15 '17 at 11:55
1

<div id="lightbox"> is inside the <body> element, thus when you scroll the lightbox you also scroll the body. The solution is to not extend the <body> element over 100%, to place the long content inside another div element and to add a scrollbar if needed to this div element with overflow: auto.

html {
  height: 100%
}
body {
  margin: 0;
  height: 100%
}
#content {
  height: 100%;
  overflow: auto;
}
#lightbox {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
<html>
  <body>
    <div id="content">much content</div>
    <div id="lightbox">lightbox<div>
  </body>
</html>

Now, scrolling over the lightbox (and the body as well) has no effect, because the body is no longer than 100% of the screen height.

Marek C
  • 11
  • 2
1

I had a similar problem: a left-hand menu that, when it appears, prevents scrolling. As soon as height was set to 100vh, the scrollbar disappeared and the content jerked to the right.

So if you don't mind keeping the scrollbar enabled (but setting the window to full height so it won't actually scroll anywhere) then another possibility is setting a tiny bottom margin, which will keep the scroll bars showing:

body {
    height: 100vh;
    overflow: hidden;
    margin: 0 0 1px;
}
dhc
  • 609
  • 8
  • 15
1

All modal/lightbox javascript-based systems use an overflow when displaying the modal/lightbox, on html tag or body tag.

When lightbox is show, the js push a overflow hidden on html or body tag. When lightbox is hidden, some remove the hidden other push a overflow auto on html or body tag.

Developers who work on Mac, do not see the problem of the scrollbar.

Just replace the hidden by an unset not to see the content slipping under the modal of the removal of the scrollbar.

Lightbox open/show:

<html style="overflow: unset;"></html>

Lightbox close/hide:

<html style="overflow: auto;"></html>
Vizor
  • 11
  • 2
1

If the page under the overlayer can be "fixed" at the top, when you open the overlay you can set

.disableScroll { position: fixed; overflow-y:scroll }

provide this class to the scrollable body, you should still see the right scrollbar but the content is not scrollable.

To maintain the position of the page do this in jquery

$('body').css('top', - ($(window).scrollTop()) + 'px').addClass('disableScroll');

When you close the overlay just revert these properties with

var top = $('body').position().top;
$('body').removeClass('disableScroll').css('top', 0).scrollTop(Math.abs(top));

I just proposed this way only because you wouldn't need to change any scroll event

Bhuvan Arora
  • 156
  • 1
  • 11
1

This will stop the viewport jumping to the top by saving the scroll position and restoring it on enabling scrolling.

CSS

.no-scroll{
  position: fixed; 
  width:100%;
  min-height:100vh;
  top:0;
  left:0;
  overflow-y:scroll!important;
}

JS

var scrollTopPostion = 0;

function scroll_pause(){
  scrollTopPostion = $(window).scrollTop();
  $("body").addClass("no-scroll").css({"top":-1*scrollTopPostion+"px"});
}

function scroll_resume(){
  $("body").removeClass("no-scroll").removeAttr("style");
  $(window).scrollTop(scrollTopPostion);
}

Now all you need to do is to call the functions

$(document).on("click","#DISABLEelementID",function(){
   scroll_pause();
});

$(document).on("click","#ENABLEelementID",function(){
   scroll_resume();
});
Kareem
  • 4,110
  • 37
  • 34
1

Another solution to get rid of content jump on fixed modal, when removing body scroll is to normalize page width:

body {width: 100vw; overflow-x: hidden;}

Then you can play with fixed position or overflow:hidden for body when the modal is open. But it will hide horizontal scrollbars - usually they're not needed on responsive website.

Iggy
  • 1,536
  • 17
  • 20
1

The position: fixed; solution has a drawback - the page jumps to the top when this style is applied. Angular's Material Dialog has a nice solution, where they fake the scroll position by applying positioning to the html element.

Below is my revised algorithm for vertical scrolling only. Left scroll blocking is done in the exact same manner.

// This class applies the following styles:
// position: fixed;
// overflow-y: scroll;
// width: 100%;
const NO_SCROLL_CLASS = "bp-no-scroll";

const coerceCssPixelValue = value => {
  if (value == null) {
    return "";
  }

  return typeof value === "string" ? value : `${value}px`;
};

export const blockScroll = () => {
  const html = document.documentElement;
  const documentRect = html.getBoundingClientRect();
  const { body } = document;

  // Cache the current scroll position to be restored later.
  const cachedScrollPosition =
    -documentRect.top || body.scrollTop || window.scrollY || document.scrollTop || 0;

  // Cache the current inline `top` value in case the user has set it.
  const cachedHTMLTop = html.style.top || "";

  // Using `html` instead of `body`, because `body` may have a user agent margin,
  // whereas `html` is guaranteed not to have one.
  html.style.top = coerceCssPixelValue(-cachedScrollPosition);

  // Set the magic class.
  html.classList.add(NO_SCROLL_CLASS);

  // Return a function to remove the scroll block.
  return () => {
    const htmlStyle = html.style;
    const bodyStyle = body.style;

    // We will need to seamlessly restore the original scroll position using
    // `window.scroll`. To do that we will change the scroll behavior to `auto`.
    // Here we cache the current scroll behavior to restore it later.
    const previousHtmlScrollBehavior = htmlStyle.scrollBehavior || "";
    const previousBodyScrollBehavior = bodyStyle.scrollBehavior || "";

    // Restore the original inline `top` value.
    htmlStyle.top = cachedHTMLTop;

    // Remove the magic class.
    html.classList.remove(NO_SCROLL_CLASS);

    // Disable user-defined smooth scrolling temporarily while we restore the scroll position.
    htmlStyle.scrollBehavior = bodyStyle.scrollBehavior = "auto";

    // Restore the original scroll position.
    window.scroll({
      top: cachedScrollPosition.top
    });

    // Restore the original scroll behavior.
    htmlStyle.scrollBehavior = previousHtmlScrollBehavior;
    bodyStyle.scrollBehavior = previousBodyScrollBehavior;
  };
};

The logic is very simple and can be simplified even more if you don't care about certain edge cases. For example, this is what I use:

export const blockScroll = () => {
  const html = document.documentElement;
  const documentRect = html.getBoundingClientRect();
  const { body } = document;
  const screenHeight = window.innerHeight;

  // Only do the magic if document is scrollable
  if (documentRect.height > screenHeight) {
    const cachedScrollPosition =
      -documentRect.top || body.scrollTop || window.scrollY || document.scrollTop || 0;

    html.style.top = coerceCssPixelValue(-cachedScrollPosition);

    html.classList.add(NO_SCROLL_CLASS);

    return () => {
      html.classList.remove(NO_SCROLL_CLASS);

      window.scroll({
        top: cachedScrollPosition,
        behavior: "auto"
      });
    };
  }
};
Avius
  • 2,702
  • 1
  • 15
  • 33
1

I have made this one function, that solves this problem with JS. This principle can be easily extended and customized that is a big pro for me.

Using this js DOM API function:

const handleWheelScroll = (element) => (event) => {
  if (!element) {
    throw Error("Element for scroll was not found");
  }
  const { deltaY } = event;
  const { clientHeight, scrollTop, scrollHeight } = element;
  if (deltaY < 0) {
    if (-deltaY > scrollTop) {
      element.scrollBy({
        top: -scrollTop,
        behavior: "smooth",
      });
      event.stopPropagation();
      event.preventDefault();
    }
    return;
  }

  if (deltaY > scrollHeight - clientHeight - scrollTop) {
    element.scrollBy({
      top: scrollHeight - clientHeight - scrollTop,
      behavior: "smooth",
    });
    event.stopPropagation();
    event.preventDefault();
    return;
  }
};

In short, this function will stop event propagation and default behavior if the scroll would scroll something else then the given element (the one you want to scroll in).

Then you can hook and unhook this up like this:

const wheelEventHandler = handleWheelScroll(elementToScrollIn);

window.addEventListener("wheel", wheelEventHandler, {
    passive: false,
});

window.removeEventListener("wheel", wheelEventHandler);

Watch out for that it is a higher order function so you have to keep a reference to the given instance.

I hook the addEventListener part in mouse enter and unhook the removeEventListener in mouse leave events in jQuery, but you can use it as you like.

LooFiiK
  • 19
  • 3
1

I’ve noticed that the YouTube website does exactly this. So by inspecting it a bit I’ve been able to determine that they’re using @polymer/iron-overlay-behavior and fortunately, it can be used rather unobtrusively outside of web components/Polymer:

import {
    pushScrollLock,
    removeScrollLock,
} from '@polymer/iron-overlay-behavior/iron-scroll-manager';

// lock scroll everywhere except scrollElement
pushScrollLock(scrollElement);

// restore scrolling
removeScrollLock(scrollElement);
  • Allows scrolling in selected element
  • Doesn't mess with styling in any way
  • Is battle-tested on YouTube website

It seems like a mature solution and surely the best I was able to find. The package is a bit heavy but I guess much of it become unbundled, when importing just the iron-scroll-manager.

Cheers

Jiří Brabec
  • 300
  • 2
  • 5
1

you can keep overflow:hidden but manage scroll position manually:

before showing keep trace of actual scroll position:

var scroll = [$(document).scrollTop(),$(document).scrollLeft()];
//show your lightbox and then reapply scroll position
$(document).scrollTop(scroll[0]).scrollLeft(scroll[1]);

it should work

malko
  • 2,137
  • 15
  • 23
0

You can do it with Javascript:

// Classic JS
window.onscroll = function(ev) {
  ev.preventDefault();
}

// jQuery
$(window).scroll(function(ev) {
  ev.preventDefault();
}

And then disable it when your lightbox is closed.

But if your lightbox contains a scroll bar, you won't be able to scroll while it's open. This is because window contains both body and #lightbox. So you have to use an architecture like the following one:

<body>
  <div id="global"></div>
  <div id="lightbox"></div>
</body>

And then apply the onscroll event only on #global.

ldiqual
  • 14,493
  • 6
  • 47
  • 87
0

Crude but working way will be to force the scroll back to top, thus effectively disabling scrolling:

var _stopScroll = false;
window.onload = function(event) {
    document.onscroll = function(ev) {
        if (_stopScroll) {
            document.body.scrollTop = "0px";
        }
    }
};

When you open the lightbox raise the flag and when closing it,lower the flag.

Live test case.

Shadow The Vaccinated Wizard
  • 62,584
  • 26
  • 129
  • 194