How to prevent page scrolling when scrolling a DIV element?
Asked Answered
F

14

99

I have reviewed and tested the various functions for preventing the body ability to scroll whilst inside a div and have combined a function that should work.

$('.scrollable').mouseenter(function() {
    $('body').bind('mousewheel DOMMouseScroll', function() {
        return false;
    });
    $(this).bind('mousewheel DOMMouseScroll', function() {
        return true;
    });
});
$('.scrollable').mouseleave(function() {
    $('body').bind('mousewheel DOMMouseScroll', function() {
        return true;
    });
});
  • This is stopping all scrolling where as I want scrolling to still be possible inside the container
  • This is also not deactivating on mouse leave

Any ideas, or better ways of doing this?

Forbes answered 29/9, 2011 at 16:31 Comment(7)
u set the body as return false from mousewheel, maybe this is the problem, i suppose your container is inside the body rightCowey
@ric_bfa yes but how to fix itForbes
instead body, set your class/id containerCowey
Perhaps I should clarify. When the mouse is inside the element '.scrollable' the body's ability to scroll should be deactivatedForbes
Isn't there a way to do this with all CSS and no JavaScript?Rhinoceros
There is a better way use the normalScrollElements option github.com/alvarotrigo/fullPage.js#optionsTartu
maybe CSS overscroll-behavior ? relatively new, so not supported by old browsers and Safari(Mac/iOS) neitherAnarch
L
170

Update 2: My solution is based on disabling the browser's native scrolling altogether (when cursor is inside the DIV) and then manually scrolling the DIV with JavaScript (by setting its .scrollTop property). An alternative and IMO better approach would be to only selectively disable the browser's scrolling in order to prevent the page scroll, but not the DIV scroll. Check out Rudie's answer below which demonstrates this solution.


Here you go:

$( '.scrollable' ).on( 'mousewheel DOMMouseScroll', function ( e ) {
    var e0 = e.originalEvent,
        delta = e0.wheelDelta || -e0.detail;

    this.scrollTop += ( delta < 0 ? 1 : -1 ) * 30;
    e.preventDefault();
});

Live demo: https://jsbin.com/howojuq/edit?js,output

So you manually set the scroll position and then just prevent the default behavior (which would be to scroll the DIV or whole web-page).

Update 1: As Chris noted in the comments below, in newer versions of jQuery, the delta information is nested within the .originalEvent object, i.e. jQuery does not expose it in its custom Event object anymore and we have to retrieve it from the native Event object instead.

Laquanda answered 29/9, 2011 at 17:2 Comment(34)
Sort of: Why did you had var delta and this.scrollTop lines, they appear unnecessaryForbes
@RobinKnight As I said, I scroll the DIV manually. You do wan't the Div to be scrollable, right?Uniplanar
Yes but the div is scrollable without those linesForbes
@RobinKnight Nope, doesn't seem so. Here is the working demo: jsfiddle.net/XNwbt/1 and here is the demo with those lines removed: jsfiddle.net/XNwbt/2Uniplanar
Your manual scrolling is backwards on Firefox 10, at least on Linux and Mac. Seems to work correctly if you make that -e.detail, tested in Firefox (Mac, Linux), Safari (Mac), and Chromium (Linux).Woe
@Woe That is correct. The sign of Mozilla's .detail does not correspond to the sign of .wheelData (which is what Chrome/IE have), so we have to invert it manually. Thanks for fixing my answer.Uniplanar
I used this, thank! I needed to add this tho: if( e.originalEvent ) e = e.originalEvent;Domiciliate
thnx @Nikso, this fixed my problem (chrome + OSX), delta had been NaN previously.Frizzell
Mate that worked like a charm for a jquery coverflow embed that was scrolling both the page and the scrollable content. Thank you.Compellation
@ŠimeVidas sadly this isn't working correctly with newer versions of jQuery (I think version>=1.7.x), even when replacing .bind with .on. The .scrollable elem is locked from scrolling too. Any idea what was changed in jQuery API that might affect it?Gerdy
@NadirSampaoli The property for both wheelDelta and detail are now nested under originalEvent. The respective third line change should look like this: var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;Armandarmanda
What's the 30? Sounds like a Magic Number... Is it 2em? My scroll is 100px, that's a big difference.Christensen
@Christensen Yes, it's a magic number. Not sure if it's feasible (or even possible) to detect the browser's current native scroll delta in a reliable cross-browser fashion.Uniplanar
Isn't this a better way then? Making xbrowser shouldn't be hard.Christensen
@Christensen Yes, it's better. Put it in an answer and I can refer to it from my answer.Uniplanar
@ŠimeVidas I made an adjustment to this after I implemented it in my software. Not critical but possibly add a sanity check for the scroll property to prevent the deadlock scrolling when the element has no scroll. if ( $(this)[0].scrollHeight !== $(this).outerHeight() ) { //yourcode } will execute only when there is a scrollbar.Armandarmanda
I have found a lot solutions, but this one is simple, easy to implement and easy to extend. Great solution! Thanks a lot.Parsons
My sole purpose is to kiss you, Mr. Šime Vidas.Museology
Something VERY interesting happened and I don't understand how. I added this snippet to my code, changed the selected to an overflowing div that I have (I specified just one), and now scrolling is disabled on the body, but magically ( didn't specify the other divs I have) ALL of the overflowing divs I have scroll beautifully. Can someone please explain this phenomena to me?Antagonism
@Antagonism Make a demo and I can debug.Uniplanar
@ŠimeVidas I've decided to prevent scrolling altogether on the page and switch to ng-views. But thank you!Antagonism
how do i implement the same functionality for devices(tablets/phones)Giddings
@ViVekH Seems to work just fine: jsbin.com/ragequ/quiet (I’ve tested in Safari/iOS)Uniplanar
@ŠimeVidas for touch devices we dont have mousescroll so this doesnt work , we need to bind for touchmove , im searching snippet for thatGiddings
@ViVekH When I swipe within the yellow element, the page is not scrolled. Please test on your touch devices and report your findings.Uniplanar
im testing on samsung galaxy tab 10 inch / chrome/ android and on scrolling the yellow element the page scrolls tooGiddings
@ViVekH I can confirm. So, in Safari, not scrolling the page while swiping within the element just happens to be the default, and we need a JavaScript snippet which would implement that behavior on Android, too.Uniplanar
I am using the code as is and it's working perfectly in IE11 and FF 40, but doesn't work correctly in Chrome 44. It does enable the scrolling of the layer div, but the body (of the main page, behind the layer) also scrolls, unlike in your jsfiddle demo where it works properly even in Chrome 44. Any idea why it doesn't work correctly in Chrome on my site? By the way, I am using jQuery 1.11.3Saylor
Solved. I needed to add e.stopPropagation(); after e.preventDefault(); for it to properly work in ChromeSaylor
Today I tried to use datetimepicker on the same page with your code and...it breaks this function...not working. What a wonderfull world of JS plugins...damn it :/ I noticed that internally DateTime plugin uses "jQuery Mouse Wheel Plugin"... which probably changes default mouse wheel capture by this function, or something else and everything breaks..damn it.Classieclassification
@Classieclassification Whoa, that seems pretty intrusive. You should be able to use the standard addEventListener method to bind the mouse wheel event.Uniplanar
@ŠimeVidas do you have any example of how it might look like? ThanksClassieclassification
@Classieclassification You’d need to start with something like this jsbin.com/zupuro/edit?js,output, and then figure out which events to bind in order to make it work in all browsers you care about. I couldn't get the original code to work in Firefox, so you'd definitely have to debug.Uniplanar
I tried this but I don't like the feel. The scrolling doesn't track well with scroll speed changes. The native implementation I think has acceleration support for the "wheel" (I am testing with 2 finger touch on a laptop), so very slow scrolling is supposed to barely move, while fast scrolling should move larger increments. With this solution the scrolling div scrolls at the same rate regardless.Sinusoidal
D
32

Use below CSS property overscroll-behavior: contain; to child element

Diplegia answered 30/9, 2019 at 10:27 Comment(2)
All of the other solutions are overkill, compared to this native CSS rule. Nicely done.Brockbrocken
The real answer.Bitchy
C
30

If you don't care about the compatibility with older IE versions (< 8), you could make a custom jQuery plugin and then call it on the overflowing element.

This solution has an advantage over the one Šime Vidas proposed, as it doesn't overwrite the scrolling behavior - it just blocks it when appropriate.

$.fn.isolatedScroll = function() {
    this.bind('mousewheel DOMMouseScroll', function (e) {
        var delta = e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.detail,
            bottomOverflow = this.scrollTop + $(this).outerHeight() - this.scrollHeight >= 0,
            topOverflow = this.scrollTop <= 0;

        if ((delta < 0 && bottomOverflow) || (delta > 0 && topOverflow)) {
            e.preventDefault();
        }
    });
    return this;
};

$('.scrollable').isolatedScroll();
Curkell answered 4/4, 2013 at 20:48 Comment(4)
Thanks! I think its more appropriate to not overwrite default behavior. To cross-browser support it can be used jQuery Mouse Wheel plugin.Insistence
Unfortunately this seems glitchy in Chrome 52 (OSX 10.10). Works perfectly in Safari, though.Phenazine
Thank you! The other solutions made the scrolling super slow and buggyAdvocacy
@wojcikstefan, I am getting this warning in chrome: [Violation] Added non-passive event listener to a scroll-blocking 'mousewheel' event. Consider marking event handler as 'passive' to make the page more responsive.Paigepaik
C
21

I think it's possible to cancel the mousescroll event sometimes: http://jsfiddle.net/rudiedirkx/F8qSq/show/

$elem.on('wheel', function(e) {
    var d = e.originalEvent.deltaY,
        dir = d < 0 ? 'up' : 'down',
        stop = (dir == 'up' && this.scrollTop == 0) || 
               (dir == 'down' && this.scrollTop == this.scrollHeight-this.offsetHeight);
    stop && e.preventDefault();
});

Inside the event handler, you'll need to know:

  • scrolling direction
    d = e.originalEvent.deltaY, dir = d < 0 ? 'up' : 'down' because a positive number means scrolling down
  • scroll position
    scrollTop for top, scrollHeight - scrollTop - offsetHeight for bottom

If you're

  • scrolling up, and top = 0, or
  • scrolling down, and bottom = 0,

cancel the event: e.preventDefault() (and maybe even e.stopPropagation()).

I think it's better to not override the browser's scrolling behaviour. Only cancel it when applicable.

It's probablt not perfectly xbrowser, but it can't be very hard. Maybe Mac's dual scroll direction is tricky though...

Christensen answered 11/12, 2013 at 13:36 Comment(1)
how do i implement the same functionality for devices(tablets/phones) i guess touchmove is the binding eventGiddings
C
3

see if this help you:

demo: jsfiddle

$('#notscroll').bind('mousewheel', function() {
     return false
});

edit:

try this:

    $("body").delegate("div.scrollable","mouseover mouseout", function(e){
       if(e.type === "mouseover"){
           $('body').bind('mousewheel',function(){
               return false;
           });
       }else if(e.type === "mouseout"){
           $('body').bind('mousewheel',function(){
               return true;
           });
       }
    });
Cowey answered 29/9, 2011 at 16:41 Comment(1)
Your elements are separate, whereas one of mine is contained in the other.Forbes
S
3

A less hacky solution, in my opinion is to set overflow hidden on the body when you mouse over the scrollable div. This will prevent the body from scrolling, but an unwanted "jumping" effect will occur. The following solution works around that:

jQuery(".scrollable")
    .mouseenter(function(e) {
        // get body width now
        var body_width = jQuery("body").width();
        // set overflow hidden on body. this will prevent it scrolling
        jQuery("body").css("overflow", "hidden"); 
        // get new body width. no scrollbar now, so it will be bigger
        var new_body_width = jQuery("body").width();
        // set the difference between new width and old width as padding to prevent jumps                                     
        jQuery("body").css("padding-right", (new_body_width - body_width)+"px");
    })
    .mouseleave(function(e) {
        jQuery("body").css({
            overflow: "auto",
            padding-right: "0px"
        });
    })

You could make your code smarter if needed. For example, you could test if the body already has a padding and if yes, add the new padding to that.

Sombrero answered 27/11, 2013 at 11:34 Comment(0)
S
3

In the solution above there is a little mistake regarding Firefox. In Firefox "DOMMouseScroll" event has no e.detail property,to get this property you should write the following 'e.originalEvent.detail'.

Here is a working solution for Firefox:

$.fn.isolatedScroll = function() {
    this.on('mousewheel DOMMouseScroll', function (e) {
        var delta = e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.originalEvent.detail,
            bottomOverflow = (this.scrollTop + $(this).outerHeight() - this.scrollHeight) >= 0,
            topOverflow = this.scrollTop <= 0;

        if ((delta < 0 && bottomOverflow) || (delta > 0 && topOverflow)) {
            e.preventDefault();
        }
    });
    return this;
};
Spessartite answered 21/10, 2015 at 7:48 Comment(1)
This works pretty well except when you crash into the top or bottom of the div the first event carries into the body. Testing with 2-finger touch on chrome in osx.Sinusoidal
U
3

here a simple solution without jQuery which does not destroy the browser native scroll (this is: no artificial/ugly scrolling):

var scrollable = document.querySelector('.scrollable');

scrollable.addEventListener('wheel', function(event) {
    var deltaY = event.deltaY;
    var contentHeight = scrollable.scrollHeight;
    var visibleHeight = scrollable.offsetHeight;
    var scrollTop = scrollable.scrollTop;

    if (scrollTop === 0 && deltaY < 0)
        event.preventDefault();
    else if (visibleHeight + scrollTop === contentHeight && deltaY > 0)
        event.preventDefault();
});

Live demo: http://jsfiddle.net/ibcaliax/bwmzfmq7/4/

Upsweep answered 23/4, 2016 at 13:21 Comment(1)
I've just published a NPM library implementing the above code: npmjs.com/package/dontscrollthebodyTitled
P
2

Here is my solution I've used in applications.

I disabled the body overflow and placed the entire website html inside container div's. The website containers have overflow and therefore the user may scroll the page as expected.

I then created a sibling div (#Prevent) with a higher z-index that covers the entire website. Since #Prevent has a higher z-index, it overlaps the website container. When #Prevent is visible the mouse is no longer hovering the website containers, so scrolling isn't possible.

You may of course place another div, such as your modal, with a higher z-index than #Prevent in the markup. This allows you to create pop-up windows that don't suffer from scrolling issues.

This solution is better because it doesn't hide the scrollbars (jumping affect). It doesn't require event listeners and it's easy to implement. It works in all browsers, although with IE7 & 8 you have to play around (depends on your specific code).

html

<body>
  <div id="YourModal" style="display:none;"></div>
  <div id="Prevent" style="display:none;"></div>
  <div id="WebsiteContainer">
     <div id="Website">
     website goes here...
     </div>
  </div>
</body>

css

body { overflow: hidden; }

#YourModal {
 z-index:200;
 /* modal styles here */
}

#Prevent {
 z-index:100;
 position:absolute;
 left:0px;
 height:100%;
 width:100%;
 background:transparent;
}

#WebsiteContainer {
  z-index:50;
  overflow:auto;
  position: absolute;
  height:100%;
  width:100%;
}
#Website {
  position:relative;
}

jquery/js

function PreventScroll(A) { 
  switch (A) {
    case 'on': $('#Prevent').show(); break;
    case 'off': $('#Prevent').hide(); break;
  }
}

disable/enable the scroll

PreventScroll('on'); // prevent scrolling
PreventScroll('off'); // allow scrolling
Phytopathology answered 26/8, 2014 at 18:38 Comment(1)
Please consider including some information about your answer, rather than simply posting code. We try to provide not just 'fixes', but help people learn. You should explain what was wrong in the original code, what you did differently, and why your change(s) worked.Igenia
E
1

I needed to add this event to multiple elements that might have a scrollbar. For the cases where no scrollbar was present, the main scrollbar didn't work as it should. So i made a small change to @Šime code as follows:

$( '.scrollable' ).on( 'mousewheel DOMMouseScroll', function ( e ) {
    if($(this).prop('scrollHeight') > $(this).height())
    {
        var e0 = e.originalEvent, delta = e0.wheelDelta || -e0.detail;

        this.scrollTop += ( delta < 0 ? 1 : -1 ) * 30;
        e.preventDefault();
    }       
});

Now, only elements with a scrollbar will prevent the main scroll from begin stopped.

Eglantine answered 11/11, 2016 at 23:14 Comment(0)
P
0

Pure javascript version of Vidas's answer, el$ is the DOM node of the plane you are scrolling.

function onlyScrollElement(event, el$) {
    var delta = event.wheelDelta || -event.detail;
    el$.scrollTop += (delta < 0 ? 1 : -1) * 10;
    event.preventDefault();
}

Make sure you dont attach the even multiple times! Here is an example,

var ul$ = document.getElementById('yo-list');
// IE9, Chrome, Safari, Opera
ul$.removeEventListener('mousewheel', onlyScrollElement);
ul$.addEventListener('mousewheel', onlyScrollElement);
// Firefox
ul$.removeEventListener('DOMMouseScroll', onlyScrollElement);
ul$.addEventListener('DOMMouseScroll', onlyScrollElement);

Word of caution, the function there needs to be a constant, if you reinitialize the function each time before attaching it, ie. var func = function (...) the removeEventListener will not work.

Platitude answered 9/9, 2014 at 8:40 Comment(0)
N
0

You can do this without JavaScript. You can set the style on both divs to position: fixed and overflow-y: auto. You may need to make one of them higher than the other by setting its z-index (if they overlap).

Here's a basic example on CodePen.

Nonie answered 20/9, 2015 at 0:14 Comment(0)
P
0

Here is the plugin that is useful for preventing parent scroll while scrolling a specific div and has a bunch of options to play with.

Check it out here:

https://github.com/MohammadYounes/jquery-scrollLock

Usage

Trigger Scroll Lock via JavaScript:

$('#target').scrollLock();

Trigger Scroll Lock via Markup:

    <!-- HTML -->
<div data-scrollLock
     data-strict='true'
     data-selector='.child'
     data-animation='{"top":"top locked","bottom":"bottom locked"}'
     data-keyboard='{"tabindex":0}'
     data-unblock='.inner'>
     ...
</div>

<!-- JavaScript -->
<script type="text/javascript">
  $('[data-scrollLock]').scrollLock()
</script>

View Demo

Preferment answered 22/9, 2018 at 16:39 Comment(0)
C
0

All you need is

e.preventDefault();

on child element.

Challenge answered 25/9, 2020 at 12:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.