How to prevent page scrolling when scrolling a DIV element?

JqueryMousewheel

Jquery Problem Overview


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?

Jquery Solutions


Solution 1 - Jquery

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.

Solution 2 - Jquery

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();

Solution 3 - Jquery

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...

Solution 4 - Jquery

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

Solution 5 - Jquery

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;
		   });
	   }
    });

Solution 6 - Jquery

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.

Solution 7 - Jquery

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;
};

Solution 8 - Jquery

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/

Solution 9 - Jquery

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

Solution 10 - Jquery

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.

Solution 11 - Jquery

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.

Solution 12 - Jquery

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.

Solution 13 - Jquery

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

Solution 14 - Jquery

All you need is

e.preventDefault();

on child element.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRIKView Question on Stackoverflow
Solution 1 - JqueryŠime VidasView Answer on Stackoverflow
Solution 2 - JquerywojcikstefanView Answer on Stackoverflow
Solution 3 - JqueryRudieView Answer on Stackoverflow
Solution 4 - JqueryPrasoon KumarView Answer on Stackoverflow
Solution 5 - JqueryRicardo BinnsView Answer on Stackoverflow
Solution 6 - JqueryAlexandru LighezanView Answer on Stackoverflow
Solution 7 - JqueryVladimir KuzminView Answer on Stackoverflow
Solution 8 - JqueryIñaki Baz CastilloView Answer on Stackoverflow
Solution 9 - JqueryDavid DView Answer on Stackoverflow
Solution 10 - JqueryRickyView Answer on Stackoverflow
Solution 11 - JquerysrcspiderView Answer on Stackoverflow
Solution 12 - JqueryCarl SmithView Answer on Stackoverflow
Solution 13 - JqueryMR_AMDEVView Answer on Stackoverflow
Solution 14 - JqueryAyberk BaytokView Answer on Stackoverflow