Reputation: 20444
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;
});
});
Any ideas, or better ways of doing this?
Upvotes: 100
Views: 136623
Reputation: 20444
2024 with wide browser support, CSS only
As of 2024 this can be accomplished entirely in CSS with wide browser support using the property overscroll-behavior. https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior
Setting this property to contain will prevent scrolling of the parent or the window entirely and maintain the scroll dynamic utilised by the browser.
Browser support is almost unanimous. https://caniuse.com/?search=overscroll-behavior
I would post a demo but the link to Mozilla perfectly demonstrates it. Set the following property to the scrollable div:
overscroll-behavior: contain;
That's all folks!
Upvotes: 2
Reputation: 186
const area = document.getElementsByClassName('area')[0];
area.addEventListener("wheel", (e)=>{
const deltaY = e.deltaY;
const scrollAmount = 250; // Adjust this value as needed
area.scrollBy({
left: deltaY < 0 ? scrollAmount : -scrollAmount,
// behavior: 'smooth'
});
e.preventDefault();
})
body{
height: 200dvh;
overflow: auto;
background-color: red;
display: flex;
align-items: center;
justify-content: center;
}
.area{
overflow: auto;
height: 50dvh;
width: 100dvw;
background-color: blue;
}
.sub_area{
height: 50dvh;
width: 200dvw;
background-color: skyblue;
}
<div class="area">
<div class="sub_area"></div>
</div>
Upvotes: 1
Reputation: 359
Use below CSS property
overscroll-behavior: contain;
to child element
Upvotes: 35
Reputation: 1922
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
Upvotes: 0
Reputation: 53831
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:
d = e.originalEvent.deltaY, dir = d < 0 ? 'up' : 'down'
because a positive number means scrolling downscrollTop
for top, scrollHeight - scrollTop - offsetHeight
for bottomIf you're
top = 0
, orbottom = 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...
Upvotes: 21
Reputation: 3072
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.
Upvotes: 1
Reputation: 185933
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.
Upvotes: 170
Reputation: 610
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/
Upvotes: 3
Reputation: 51
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;
};
Upvotes: 3
Reputation: 3070
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.
Upvotes: 0
Reputation: 11205
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.
Upvotes: 0
Reputation: 1295
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
Upvotes: 2
Reputation: 112
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.
Upvotes: 3
Reputation: 887
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();
Upvotes: 30
Reputation: 3246
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;
});
}
});
Upvotes: 3