Prevent scrolling of parent element when inner element scroll position reaches top/bottom?
I am adding this answer for completeness because the accepted answer by @amustill does not correctly solve the problem in Internet Explorer. Please see the comments in my original post for details. In addition, this solution does not require any plugins - only jQuery.
In essence, the code works by handling the mousewheel
event. Each such event contains a wheelDelta
equal to the number of px
which it is going to move the scrollable area to. If this value is >0
, then we are scrolling up
. If the wheelDelta
is <0
then we are scrolling down
.
FireFox: FireFox uses DOMMouseScroll
as the event, and populates originalEvent.detail
, whose +/-
is reversed from what is described above. It generally returns intervals of 3
, while other browsers return scrolling in intervals of 120
(at least on my machine). To correct, we simply detect it and multiply by -40
to normalize.
@amustill's answer works by canceling the event if the <div>
's scrollable area is already either at the top or the bottom maximum position. However, Internet Explorer disregards the canceled event in situations where the delta
is larger than the remaining scrollable space.
In other words, if you have a 200px
tall <div>
containing 500px
of scrollable content, and the current scrollTop
is 400
, a mousewheel
event which tells the browser to scroll 120px
further will result in both the <div>
and the <body>
scrolling, because 400
+ 120
> 500
.
So - to solve the problem, we have to do something slightly different, as shown below:
The requisite jQuery
code is:
$(document).on('DOMMouseScroll mousewheel', '.Scrollable', function(ev) {
var $this = $(this),
scrollTop = this.scrollTop,
scrollHeight = this.scrollHeight,
height = $this.innerHeight(),
delta = (ev.type == 'DOMMouseScroll' ?
ev.originalEvent.detail * -40 :
ev.originalEvent.wheelDelta),
up = delta > 0;
var prevent = function() {
ev.stopPropagation();
ev.preventDefault();
ev.returnValue = false;
return false;
}
if (!up && -delta > scrollHeight - height - scrollTop) {
// Scrolling down, but this will take us past the bottom.
$this.scrollTop(scrollHeight);
return prevent();
} else if (up && delta > scrollTop) {
// Scrolling up, but this will take us past the top.
$this.scrollTop(0);
return prevent();
}
});
In essence, this code cancels any scrolling event which would create the unwanted edge condition, then uses jQuery to set the scrollTop
of the <div>
to either the maximum or minimum value, depending on which direction the mousewheel
event was requesting.
Because the event is canceled entirely in either case, it never propagates to the body
at all, and therefore solves the issue in IE, as well as all of the other browsers.
I have also put up a working example on jsFiddle.
It's possible with the use of Brandon Aaron's Mousewheel plugin.
Here's a demo: http://jsbin.com/jivutakama/edit?html,js,output
$(function() {
var toolbox = $('#toolbox'),
height = toolbox.height(),
scrollHeight = toolbox.get(0).scrollHeight;
toolbox.bind('mousewheel', function(e, d) {
if((this.scrollTop === (scrollHeight - height) && d < 0) || (this.scrollTop === 0 && d > 0)) {
e.preventDefault();
}
});
});