This question already has answers here:
Check if element is visible in DOM
(27 answers)
Closed 6 years ago.
In JavaScript, how would you check if an element is actually visible?
I don't just mean checking the visibility and display attributes. I mean, checking that the element is not
visibility: hidden or display: none
underneath another element
scrolled off the edge of the screen
For technical reasons I can't include any scripts. I can however use Prototype as it is on the page already.
For the point 2.
I see that no one has suggested to use document.elementFromPoint(x,y), to me it is the fastest way to test if an element is nested or hidden by another. You can pass the offsets of the targetted element to the function.
Here's PPK test page on elementFromPoint.
From MDN's documentation:
The elementFromPoint() method—available on both the Document and ShadowRoot objects—returns the topmost Element at the specified coordinates (relative to the viewport).
I don't know how much of this is supported in older or not-so-modern browsers, but I'm using something like this (without the neeed for any libraries):
function visible(element) {
if (element.offsetWidth === 0 || element.offsetHeight === 0) return false;
var height = document.documentElement.clientHeight,
rects = element.getClientRects(),
on_top = function(r) {
var x = (r.left + r.right)/2, y = (r.top + r.bottom)/2;
return document.elementFromPoint(x, y) === element;
};
for (var i = 0, l = rects.length; i < l; i++) {
var r = rects[i],
in_viewport = r.top > 0 ? r.top <= height : (r.bottom > 0 && r.bottom <= height);
if (in_viewport && on_top(r)) return true;
}
return false;
}
It checks that the element has an area > 0 and then it checks if any part of the element is within the viewport and that it is not hidden "under" another element (actually I only check on a single point in the center of the element, so it's not 100% assured -- but you could just modify the script to itterate over all the points of the element, if you really need to...).
Update
Modified on_top function that check every pixel:
on_top = function(r) {
for (var x = Math.floor(r.left), x_max = Math.ceil(r.right); x <= x_max; x++)
for (var y = Math.floor(r.top), y_max = Math.ceil(r.bottom); y <= y_max; y++) {
if (document.elementFromPoint(x, y) === element) return true;
}
return false;
};
Don't know about the performance :)
As jkl pointed out, checking the element's visibility or display is not enough. You do have to check its ancestors. Selenium does this when it verifies visibility on an element.
Check out the method Selenium.prototype.isVisible in the selenium-api.js file.
http://svn.openqa.org/svn/selenium-on-rails/selenium-on-rails/selenium-core/scripts/selenium-api.js
Interesting question.
This would be my approach.
At first check that element.style.visibility !== 'hidden' && element.style.display !== 'none'
Then test with document.elementFromPoint(element.offsetLeft, element.offsetTop) if the returned element is the element I expect, this is tricky to detect if an element is overlapping another completely.
Finally test if offsetTop and offsetLeft are located in the viewport taking scroll offsets into account.
Hope it helps.
This is what I have so far. It covers both 1 and 3. I'm however still struggling with 2 since I'm not that familiar with Prototype (I'm more a jQuery type of guy).
function isVisible( elem ) {
var $elem = $(elem);
// First check if elem is hidden through css as this is not very costly:
if ($elem.getStyle('display') == 'none' || $elem.getStyle('visibility') == 'hidden' ) {
//elem is set through CSS stylesheet or inline to invisible
return false;
}
//Now check for the elem being outside of the viewport
var $elemOffset = $elem.viewportOffset();
if ($elemOffset.left < 0 || $elemOffset.top < 0) {
//elem is left of or above viewport
return false;
}
var vp = document.viewport.getDimensions();
if ($elemOffset.left > vp.width || $elemOffset.top > vp.height) {
//elem is below or right of vp
return false;
}
//Now check for elements positioned on top:
//TODO: Build check for this using Prototype...
//Neither of these was true, so the elem was visible:
return true;
}
/**
* Checks display and visibility of elements and it's parents
* #param DomElement el
* #param boolean isDeep Watch parents? Default is true
* #return {Boolean}
*
* #author Oleksandr Knyga <oleksandrknyga#gmail.com>
*/
function isVisible(el, isDeep) {
var elIsVisible = true;
if("undefined" === typeof isDeep) {
isDeep = true;
}
elIsVisible = elIsVisible && el.offsetWidth > 0 && el.offsetHeight > 0;
if(isDeep && elIsVisible) {
while('BODY' != el.tagName && elIsVisible) {
elIsVisible = elIsVisible && 'hidden' != window.getComputedStyle(el).visibility;
el = el.parentElement;
}
}
return elIsVisible;
}
You can use the clientHeight or clientWidth properties
function isViewable(element){
return (element.clientHeight > 0);
}
Prototype's Element library is one of the most powerful query libraries in terms of the methods. I recommend you to check out the API.
A few hints:
Checking visibility can be a pain, but you can use the Element.getStyle() method and Element.visible() methods combined into a custom function. With getStyle() you can check the actual computed style.
I don't know exactly what you mean by "underneath" :) If you meant by it has a specific ancestor, for example, a wrapper div, you can use Element.up(cssRule):
var child = $("myparagraph");
if(!child.up("mywrapper")){
// I lost my mom!
}
else {
// I found my mom!
}
If you want to check the siblings of the child element you can do that too:
var child = $("myparagraph");
if(!child.previous("mywrapper")){
// I lost my bro!
}
else {
// I found my bro!
}
Again, Element lib can help you if I understand correctly what you mean :) You can check the actual dimensions of the viewport and the offset of your element so you can calculate if your element is "off screen".
Good luck!
I pasted a test case for prototypejs at http://gist.github.com/117125. It seems in your case we simply cannot trust in getStyle() at all. For maximizing the reliability of the isMyElementReallyVisible function you should combine the following:
Checking the computed style (dojo has a nice implementation that you can borrow)
Checking the viewportoffset (prototype native method)
Checking the z-index for the "beneath" problem (under Internet Explorer it may be buggy)
One way to do it is:
isVisible(elm) {
while(elm.tagName != 'BODY') {
if(!$(elm).visible()) return false;
elm = elm.parentNode;
}
return true;
}
Credits: https://github.com/atetlaw/Really-Easy-Field-Validation/blob/master/validation.js#L178
Try element.getBoundingClientRect().
It will return an object with properties
bottom
top
right
left
width -- browser dependent
height -- browser dependent
Check that the width and height of the element's BoundingClientRect are not zero which is the value of hidden or non-visible elements. If the values are greater than zero the element should be visible in the body. Then check if the bottom property is less than screen.height which would imply that the element is withing the viewport. (Technically you would also have to account for the top of the browser window including the searchbar, buttons, etc.)
Catch mouse-drag and viewport events (onmouseup, onresize, onscroll).
When a drag ends do a comparison of the dragged item boundary with all "elements of interest" (ie, elements with class "dont_hide" or an array of ids). Do the same with window.onscroll and window.onresize. Mark any elements hidden with a special attribute or classname or simply perform whatever action you want then and there.
The hidden tests are pretty easy. For "totally hidden" you want to know if ALL corners are either inside the dragged-item boundary or outside the viewport. For partially hidden you're looking for a single corner matching the same test.
I don't think checking the element's own visibility and display properties is good enough for requirement #1, even if you use currentStyle/getComputedStyle. You also have to check the element's ancestors. If an ancestor is hidden, so is the element.
Check elements' offsetHeight property. If it is more than 0, it is visible. Note: this approach doesn't cover a situation when visibility:hidden style is set. But that style is something weird anyways.
Here is a sample script and test case. Covers positioned elements, visibilty: hidden, display: none. Didn't test z-index, assume it works.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<style type="text/css">
div {
width: 200px;
border: 1px solid red;
}
p {
border: 2px solid green;
}
.r {
border: 1px solid #BB3333;
background: #EE9999;
position: relative;
top: -50px;
height: 2em;
}
.of {
overflow: hidden;
height: 2em;
word-wrap: none;
}
.of p {
width: 100%;
}
.of pre {
display: inline;
}
.iv {
visibility: hidden;
}
.dn {
display: none;
}
</style>
<script src="http://www.prototypejs.org/assets/2008/9/29/prototype-1.6.0.3.js"></script>
<script>
function isVisible(elem){
if (Element.getStyle(elem, 'visibility') == 'hidden' || Element.getStyle(elem, 'display') == 'none') {
return false;
}
var topx, topy, botx, boty;
var offset = Element.positionedOffset(elem);
topx = offset.left;
topy = offset.top;
botx = Element.getWidth(elem) + topx;
boty = Element.getHeight(elem) + topy;
var v = false;
for (var x = topx; x <= botx; x++) {
for(var y = topy; y <= boty; y++) {
if (document.elementFromPoint(x,y) == elem) {
// item is visible
v = true;
break;
}
}
if (v == true) {
break;
}
}
return v;
}
window.onload=function() {
var es = Element.descendants('body');
for (var i = 0; i < es.length; i++ ) {
if (!isVisible(es[i])) {
alert(es[i].tagName);
}
}
}
</script>
</head>
<body id='body'>
<div class="s"><p>This is text</p><p>More text</p></div>
<div class="r">This is relative</div>
<div class="of"><p>This is too wide...</p><pre>hidden</pre>
<div class="iv">This is invisible</div>
<div class="dn">This is display none</div>
</body>
</html>
Here is a part of the response that tells you if an element is in the viewport.
You may need to check if there is nothing on top of it using elementFromPoint, but it's a bit longer.
function isInViewport(element) {
var rect = element.getBoundingClientRect();
var windowHeight = window.innerHeight || document.documentElement.clientHeight;
var windowWidth = window.innerWidth || document.documentElement.clientWidth;
return rect.bottom > 0 && rect.top < windowHeight && rect.right > 0 && rect.left < windowWidth;
}
I have this event:
$(window).scroll(function(e){
console.log(e);
})
I want to know, how much I have scroll value in pixels, because I think, scroll value depends from window size and screen resolution.
Function parameter e does not contains this information.
I can store $(window).scrollTop() after every scroll and calculate difference, but can I do it differently?
The "scroll value" does not depend on the window size or screen resolution. The "scroll value" is simply the number of pixels scrolled.
However, whether you are able to scroll at all, and the amount you can scroll is based on available real estate for the container and the dimensions of the content within the container (in this case the container is document.documentElement, or document.body for older browsers).
You are correct that the scroll event does not contain this information. It does not provide a delta property to indicate the number of pixels scrolled. This is true for the native scroll event and the jQuery scroll event. This seems like it would be a useful feature to have, similar to how mousewheel events provide properties for X and Y delta.
I do not know, and will not speculate upon, why the powers-that-be did not provide a delta property for scroll, but that is out of scope for this question (feel free to post a separate question about this).
The method you are using of storing scrollTop in a variable and comparing it to the current scrollTop is the best (and only) method I have found. However, you can simplify this a bit by extending jQuery to provide a new custom event, per this article: http://learn.jquery.com/events/event-extensions/
Here is an example extension I created that works with window / document scrolling. It is a custom event called scrolldelta that automatically tracks the X and Y delta (as scrollLeftDelta and scrollTopDelta, respectively). I have not tried it with other elements; leaving this as exercise for the reader. This works in currrent versions of Chrome and Firefox. It uses the trick for getting the sum of document.documentElement.scrollTop and document.body.scrollTop to handle the bug where Chrome updates body.scrollTop instead of documentElement.scrollTop (IE and FF update documentElement.scrollTop; see https://code.google.com/p/chromium/issues/detail?id=2891).
JSFiddle demo: http://jsfiddle.net/tew9zxc1/
Runnable Snippet (scroll down and click Run code snippet):
// custom 'scrolldelta' event extends 'scroll' event
jQuery.event.special.scrolldelta = {
delegateType: "scroll",
bindType: "scroll",
handle: function (event) {
var handleObj = event.handleObj;
var targetData = jQuery.data(event.target);
var ret = null;
var elem = event.target;
var isDoc = elem === document;
var oldTop = targetData.top || 0;
var oldLeft = targetData.left || 0;
targetData.top = isDoc ? elem.documentElement.scrollTop + elem.body.scrollTop : elem.scrollTop;
targetData.left = isDoc ? elem.documentElement.scrollLeft + elem.body.scrollLeft : elem.scrollLeft;
event.scrollTopDelta = targetData.top - oldTop;
event.scrollTop = targetData.top;
event.scrollLeftDelta = targetData.left - oldLeft;
event.scrollLeft = targetData.left;
event.type = handleObj.origType;
ret = handleObj.handler.apply(this, arguments);
event.type = handleObj.type;
return ret;
}
};
// bind to custom 'scrolldelta' event
$(window).on('scrolldelta', function (e) {
var top = e.scrollTop;
var topDelta = e.scrollTopDelta;
var left = e.scrollLeft;
var leftDelta = e.scrollLeftDelta;
// do stuff with the above info; for now just display it to user
var feedbackText = 'scrollTop: ' + top.toString() + 'px (' + (topDelta >= 0 ? '+' : '') + topDelta.toString() + 'px), scrollLeft: ' + left.toString() + 'px (' + (leftDelta >= 0 ? '+' : '') + leftDelta.toString() + 'px)';
document.getElementById('feedback').innerHTML = feedbackText;
});
#content {
/* make window tall enough for vertical scroll */
height: 2000px;
/* make window wide enough for horizontal scroll */
width: 2000px;
/* visualization of scrollable content */
background-color: blue;
}
#feedback {
border:2px solid red;
padding: 4px;
color: black;
position: fixed;
top: 0;
height: 20px;
background-color: #fff;
font-family:'Segoe UI', 'Arial';
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='feedback'>scrollTop: 0px, scrollLeft: 0px</div>
<div id='content'></div>
Note that you may want debounce the event depending on what you are doing. You didn't provide very much context in your question, but if you give a better example of what you are actually using this info for we can provide a better answer. (Please show more of your code, and how you are using the "scroll value").
To detemine how many pixels were scrolled you have to keep in mind that the scroll event gets fired almost every pixel that you move. The way to accomplish it is to save the previous scrolled value and compare that in a timeout. Like this:
var scrollValue = 0;
var scrollTimeout = false
$(window).scroll(function(event){
/* Clear it so the function only triggers when scroll events have stopped firing*/
clearTimeout(scrollTimeout);
/* Set it so it fires after a second, but gets cleared after a new triggered event*/
scrollTimeout = setTimeout(function(){
var scrolled = $(document).scrollTop() - scrollValue;
scrollValue = $(document).scrollTop();
alert("The value scrolled was " + scrolled);
}, 1000);
});
This way you will get the amount of scrolled a second after scrolling (this is adjustable but you have to keep in mind that the smooth scrolling that is so prevalent today has some run-out time and you dont want to trigger before a full stop).
The other way to do this? Yes, possible, with jQuery Mobile
I do not appreciate this solution, because it is necessary to include heavy jQuery mobile. Solution:
var diff, top = 0;
$(document).on("scrollstart",function () {
// event fired when scrolling is started
top = $(window).scrollTop();
});
$(document).on("scrollstop",function () {
// event fired when scrolling is stopped
diff = Math.abs($(window).scrollTop() - top);
});
To reduce the used processing power by adding a timer to a Jquery scroll method is probably not a great idea. The visual effect is indeed quite bad.
The whole web browsing experience could be made much better by hiding the scrolling element just when the scroll begins and making it slide in (at the right position) some time after. The scrolling even can be checked with a delay too.
This solution works great.
$(document).ready(function() {
var element = $('.movable_div'),
originalY = element.offset().top;
element.css('position', 'relative');
$(window).on('scroll', function(event) {
var scrollTop = $(window).scrollTop();
element.hide();
element.stop(false, false).animate({
top: scrollTop < originalY
? 0
: scrollTop - originalY + 35
}, 2000,function(){element.slideDown(500,"swing");});
});
});
Live demo here
I have a little "floating tool box" - a div with position:fixed; overflow:auto.
Works just fine.
But when scrolling inside that box (with the mouse wheel) and reaching the bottom OR top, the parent element "takes over" the "scroll request" : The document behind the tool box scrolls.
- Which is annoying and not what the user "asked for".
I'm using jQuery and thought I could stop this behaviour with event.stoppropagation():
$("#toolBox").scroll( function(event){ event.stoppropagation() });
It does enter the function, but still, propagation happens anyway (the document scrolls)
- It's surprisingly hard to search for this topic on SO (and Google), so I have to ask:
How to prevent propagation / bubbling of the scroll-event ?
Edit:
Working solution thanks to amustill (and Brandon Aaron for the mousewheel-plugin here:
https://github.com/brandonaaron/jquery-mousewheel/raw/master/jquery.mousewheel.js
$(".ToolPage").bind('mousewheel', function(e, d)
var t = $(this);
if (d > 0 && t.scrollTop() === 0) {
e.preventDefault();
}
else {
if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) {
e.preventDefault();
}
}
});
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.
All the solutions given in this thread don't mention an existing - and native - way to solve this problem without reordering DOM and/or using event preventing tricks. But there's a good reason: this way is proprietary - and available on MS web platform only. Quoting MSDN:
-ms-scroll-chaining property - specifies the scrolling behavior that occurs when a user hits the scroll limit during a manipulation. Property values:
chained - Initial value. The nearest scrollable parent element begins scrolling when the user hits a scroll limit during a manipulation. No bounce effect is shown.
none - A bounce effect is shown when the user hits a scroll limit during a manipulation.
Granted, this property is supported on IE10+/Edge only. Still, here's a telling quote:
To give you a sense of how popular preventing scroll chaining may be,
according to my quick http-archive search "-ms-scroll-chaining: none"
is used in 0.4% of top 300K pages despite being limited in
functionality and only supported on IE/Edge.
And now good news, everyone! Starting from Chrome 63, we finally have a native cure for Blink-based platforms too - and that's both Chrome (obviously) and Android WebView (soon).
Quoting the introducing article:
The overscroll-behavior property is a new CSS feature that controls
the behavior of what happens when you over-scroll a container
(including the page itself). You can use it to cancel scroll chaining,
disable/customize the pull-to-refresh action, disable rubberbanding
effects on iOS (when Safari implements overscroll-behavior), and more.[...]
The property takes three possible values:
auto - Default. Scrolls that originate on the element may propagate to
ancestor elements.
contain - prevents scroll chaining. Scrolls do not
propagate to ancestors but local effects within the node are shown.
For example, the overscroll glow effect on Android or the
rubberbanding effect on iOS which notifies the user when they've hit a
scroll boundary. Note: using overscroll-behavior: contain on the html
element prevents overscroll navigation actions.
none - same as contain but it also prevents overscroll effects within the node itself (e.g. Android overscroll glow or iOS rubberbanding).
[...] The best part is that using overscroll-behavior does not adversely
affect page performance like the hacks mentioned in the intro!
Here's this feature in action. And here's corresponding CSS Module document.
UPDATE: Firefox, since version 59, has joined the club, and MS Edge is expected to implement this feature in version 18. Here's the corresponding caniusage.
UPDATE 2: And now (Oct, 2022) Safari officially joined the club: since 16.0 version, overscroll-behavior is no longer behind the feature flag.
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();
}
});
});
I know it's quite an old question, but since this is one of top results in google... I had to somehow cancel scroll bubbling without jQuery and this code works for me:
function preventDefault(e) {
e = e || window.event;
if (e.preventDefault)
e.preventDefault();
e.returnValue = false;
}
document.getElementById('a').onmousewheel = function(e) {
document.getElementById('a').scrollTop -= e. wheelDeltaY;
preventDefault(e);
}
EDIT: CodePen example
For AngularJS, I defined the following directive:
module.directive('isolateScrolling', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.bind('DOMMouseScroll', function (e) {
if (e.detail > 0 && this.clientHeight + this.scrollTop == this.scrollHeight) {
this.scrollTop = this.scrollHeight - this.clientHeight;
e.stopPropagation();
e.preventDefault();
return false;
}
else if (e.detail < 0 && this.scrollTop <= 0) {
this.scrollTop = 0;
e.stopPropagation();
e.preventDefault();
return false;
}
});
element.bind('mousewheel', function (e) {
if (e.deltaY > 0 && this.clientHeight + this.scrollTop >= this.scrollHeight) {
this.scrollTop = this.scrollHeight - this.clientHeight;
e.stopPropagation();
e.preventDefault();
return false;
}
else if (e.deltaY < 0 && this.scrollTop <= 0) {
this.scrollTop = 0;
e.stopPropagation();
e.preventDefault();
return false;
}
return true;
});
}
};
});
And then added it to the scrollable element (the dropdown-menu ul):
<div class="dropdown">
<button type="button" class="btn dropdown-toggle">Rename <span class="caret"></span></button>
<ul class="dropdown-menu" isolate-scrolling>
<li ng-repeat="s in savedSettings | objectToArray | orderBy:'name' track by s.name">
<a ng-click="renameSettings(s.name)">{{s.name}}</a>
</li>
</ul>
</div>
Tested on Chrome and Firefox. Chrome's smooth scrolling defeats this hack when a large mousewheel movement is made near (but not at) the top or bottom of the scroll region.
There are tons of questions like this out there, with many answers, but I could not find a satisfactory solution that did not involve events, scripts, plugins, etc. I wanted to keep it straight in HTML and CSS. I finally found a solution that worked, although it involved restructuring the markup to break the event chain.
1. Basic problem
Scrolling input (i.e.: mousewheel) applied to the modal element will spill over into an ancestor element and scroll it in the same direction, if some such element is scrollable:
(All examples are meant to be viewed on desktop resolutions)
https://jsfiddle.net/ybkbg26c/5/
HTML:
<div id="parent">
<div id="modal">
This text is pretty long here. Hope fully, we will get some scroll bars.
</div>
</div>
CSS:
#modal {
position: absolute;
height: 100px;
width: 100px;
top: 20%;
left: 20%;
overflow-y: scroll;
}
#parent {
height: 4000px;
}
2. No parent scroll on modal scroll
The reason why the ancestor ends up scrolling is because the scroll event bubbles and some element on the chain is able to handle it. A way to stop that is to make sure none of the elements on the chain know how to handle the scroll. In terms of our example, we can refactor the tree to move the modal out of the parent element. For obscure reasons, it is not enough to keep the parent and the modal DOM siblings; the parent must be wrapped by another element that establishes a new stacking context. An absolutely positioned wrapper around the parent can do the trick.
The result we get is that as long as the modal receives the scroll event, the event will not bubble to the "parent" element.
It should typically be possible to redesign the DOM tree to support this behavior without affecting what the end user sees.
https://jsfiddle.net/0bqq31Lv/3/
HTML:
<div id="context">
<div id="parent">
</div>
</div>
<div id="modal">
This text is pretty long here. Hope fully, we will get some scroll bars.
</div>
CSS (new only):
#context {
position: absolute;
overflow-y: scroll;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
3. No scroll anywhere except in modal while it is up
The solution above still allows the parent to receive scroll events, as long as they are not intercepted by the modal window (i.e. if triggered by mousewheel while the cursor is not over the modal). This is sometimes undesirable and we may want to forbid all background scrolling while the modal is up. To do that, we need to insert an extra stacking context that spans the whole viewport behind the modal. We can do that by displaying an absolutely positioned overlay, which can be fully transparent if necessary (but not visibility:hidden).
https://jsfiddle.net/0bqq31Lv/2/
HTML:
<div id="context">
<div id="parent">
</div>
</div>
<div id="overlay">
</div>
<div id="modal">
This text is pretty long here. Hope fully, we will get some scroll bars.
</div>
CSS (new on top of #2):
#overlay {
background-color: transparent;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
Here's a plain JavaScript version:
function scroll(e) {
var delta = (e.type === "mousewheel") ? e.wheelDelta : e.detail * -40;
if (delta < 0 && (this.scrollHeight - this.offsetHeight - this.scrollTop) <= 0) {
this.scrollTop = this.scrollHeight;
e.preventDefault();
} else if (delta > 0 && delta > this.scrollTop) {
this.scrollTop = 0;
e.preventDefault();
}
}
document.querySelectorAll(".scroller").addEventListener("mousewheel", scroll);
document.querySelectorAll(".scroller").addEventListener("DOMMouseScroll", scroll);
As variant, to avoid performance issues with scroll or mousewheel handling, you can use code like below:
css:
body.noscroll {
overflow: hidden;
}
.scrollable {
max-height: 200px;
overflow-y: scroll;
border: 1px solid #ccc;
}
html:
<div class="scrollable">
...A bunch of items to make the div scroll...
</div>
...A bunch of text to make the body scroll...
js:
var $document = $(document),
$body = $('body'),
$scrolable = $('.scrollable');
$scrolable.on({
'mouseenter': function () {
// add hack class to prevent workspace scroll when scroll outside
$body.addClass('noscroll');
},
'mouseleave': function () {
// remove hack class to allow scroll
$body.removeClass('noscroll');
}
});
Example of work: http://jsbin.com/damuwinarata/4
Angular JS Directive
I had to wrap an angular directive. The following is a Mashup of the other answers here. tested on Chrome and Internet Explorer 11.
var app = angular.module('myApp');
app.directive("preventParentScroll", function () {
return {
restrict: "A",
scope: false,
link: function (scope, elm, attr) {
elm.bind('mousewheel', onMouseWheel);
function onMouseWheel(e) {
elm[0].scrollTop -= (e.wheelDeltaY || (e.originalEvent && (e.originalEvent.wheelDeltaY || e.originalEvent.wheelDelta)) || e.wheelDelta || 0);
e.stopPropagation();
e.preventDefault();
e.returnValue = false;
}
}
}
});
Usage
<div prevent-parent-scroll>
...
</div>
Hopes this helps the next person that gets here from a Google search.
Using native element scroll properties with the delta value from the mousewheel plugin:
$elem.on('mousewheel', function (e, delta) {
// Restricts mouse scrolling to the scrolling range of this element.
if (
this.scrollTop < 1 && delta > 0 ||
(this.clientHeight + this.scrollTop) === this.scrollHeight && delta < 0
) {
e.preventDefault();
}
});
In case someone is still looking for a solution for this, the following plugin does the job http://mohammadyounes.github.io/jquery-scrollLock/
It fully addresses the issue of locking mouse wheel scroll inside a given container, preventing it from propagating to parent element.
It does not change wheel scrolling speed, user experience will not be affected. and you get the same behavior regardless of the OS mouse wheel vertical scrolling speed (On Windows it can be set to one screen or one line up to 100 lines per notch).
Demo: http://mohammadyounes.github.io/jquery-scrollLock/example/
Source: https://github.com/MohammadYounes/jquery-scrollLock
You can achieve this outcome with CSS, ie
.isolate-scrolling {
overscroll-behavior: contain;
}
This will only scroll the parent container if your mouse leaves the child element to the parent.
amustill's answer as a knockout handler:
ko.bindingHandlers.preventParentScroll = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
$(element).mousewheel(function (e, d) {
var t = $(this);
if (d > 0 && t.scrollTop() === 0) {
e.preventDefault();
}
else {
if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) {
e.preventDefault();
}
}
});
}
};
the method above is not that natural, after some googling I find a more nice solution , and no need of jQuery. see [1] and demo [2].
var element = document.getElementById('uf-notice-ul');
var isMacWebkit = (navigator.userAgent.indexOf("Macintosh") !== -1 &&
navigator.userAgent.indexOf("WebKit") !== -1);
var isFirefox = (navigator.userAgent.indexOf("firefox") !== -1);
element.onwheel = wheelHandler; // Future browsers
element.onmousewheel = wheelHandler; // Most current browsers
if (isFirefox) {
element.scrollTop = 0;
element.addEventListener("DOMMouseScroll", wheelHandler, false);
}
// prevent from scrolling parrent elements
function wheelHandler(event) {
var e = event || window.event; // Standard or IE event object
// Extract the amount of rotation from the event object, looking
// for properties of a wheel event object, a mousewheel event object
// (in both its 2D and 1D forms), and the Firefox DOMMouseScroll event.
// Scale the deltas so that one "click" toward the screen is 30 pixels.
// If future browsers fire both "wheel" and "mousewheel" for the same
// event, we'll end up double-counting it here. Hopefully, however,
// cancelling the wheel event will prevent generation of mousewheel.
var deltaX = e.deltaX * -30 || // wheel event
e.wheelDeltaX / 4 || // mousewheel
0; // property not defined
var deltaY = e.deltaY * -30 || // wheel event
e.wheelDeltaY / 4 || // mousewheel event in Webkit
(e.wheelDeltaY === undefined && // if there is no 2D property then
e.wheelDelta / 4) || // use the 1D wheel property
e.detail * -10 || // Firefox DOMMouseScroll event
0; // property not defined
// Most browsers generate one event with delta 120 per mousewheel click.
// On Macs, however, the mousewheels seem to be velocity-sensitive and
// the delta values are often larger multiples of 120, at
// least with the Apple Mouse. Use browser-testing to defeat this.
if (isMacWebkit) {
deltaX /= 30;
deltaY /= 30;
}
e.currentTarget.scrollTop -= deltaY;
// If we ever get a mousewheel or wheel event in (a future version of)
// Firefox, then we don't need DOMMouseScroll anymore.
if (isFirefox && e.type !== "DOMMouseScroll") {
element.removeEventListener("DOMMouseScroll", wheelHandler, false);
}
// Don't let this event bubble. Prevent any default action.
// This stops the browser from using the mousewheel event to scroll
// the document. Hopefully calling preventDefault() on a wheel event
// will also prevent the generation of a mousewheel event for the
// same rotation.
if (e.preventDefault) e.preventDefault();
if (e.stopPropagation) e.stopPropagation();
e.cancelBubble = true; // IE events
e.returnValue = false; // IE events
return false;
}
[1] https://dimakuzmich.wordpress.com/2013/07/16/prevent-scrolling-of-parent-element-with-javascript/
[2] http://jsfiddle.net/dima_k/5mPkB/1/
This actually works in AngularJS.
Tested on Chrome and Firefox.
.directive('stopScroll', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.bind('mousewheel', function (e) {
var $this = $(this),
scrollTop = this.scrollTop,
scrollHeight = this.scrollHeight,
height = $this.height(),
delta = (e.type == 'DOMMouseScroll' ?
e.originalEvent.detail * -40 :
e.originalEvent.wheelDelta),
up = delta > 0;
var prevent = function() {
e.stopPropagation();
e.preventDefault();
e.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();
}
});
}
};
})
my jQuery plugin:
$('.child').dontScrollParent();
$.fn.dontScrollParent = function()
{
this.bind('mousewheel DOMMouseScroll',function(e)
{
var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;
if (delta > 0 && $(this).scrollTop() <= 0)
return false;
if (delta < 0 && $(this).scrollTop() >= this.scrollHeight - $(this).height())
return false;
return true;
});
}
I have a similar situation and here's how i solved it:
All my scrollable elements get the class scrollable.
$(document).on('wheel', '.scrollable', function(evt) {
var offsetTop = this.scrollTop + parseInt(evt.originalEvent.deltaY, 10);
var offsetBottom = this.scrollHeight - this.getBoundingClientRect().height - offsetTop;
if (offsetTop < 0 || offsetBottom < 0) {
evt.preventDefault();
} else {
evt.stopImmediatePropagation();
}
});
stopImmediatePropagation() makes sure not to scroll parent scrollable area from scrollable child area.
Here's a vanilla JS implementation of it:
http://jsbin.com/lugim/2/edit?js,output
New web dev here. This worked like a charm for me on both IE and Chrome.
static preventScrollPropagation(e: HTMLElement) {
e.onmousewheel = (ev) => {
var preventScroll = false;
var isScrollingDown = ev.wheelDelta < 0;
if (isScrollingDown) {
var isAtBottom = e.scrollTop + e.clientHeight == e.scrollHeight;
if (isAtBottom) {
preventScroll = true;
}
} else {
var isAtTop = e.scrollTop == 0;
if (isAtTop) {
preventScroll = true;
}
}
if (preventScroll) {
ev.preventDefault();
}
}
}
Don't let the number of lines fool you, it is quite simple - just a bit verbose for readability (self documenting code ftw right?)
Also I should mention that the language here is TypeScript, but as always, it is straightforward to convert it to JS.
We can simply use CSS.
Give a style to the child scroll container element.
style="overscroll-behavior: contain"
It doesn't trigger the parent's scroll event.
For those using MooTools, here is equivalent code:
'mousewheel': function(event){
var height = this.getSize().y;
height -= 2; // Not sure why I need this bodge
if ((this.scrollTop === (this.scrollHeight - height) && event.wheel < 0) ||
(this.scrollTop === 0 && event.wheel > 0)) {
event.preventDefault();
}
Bear in mind that I, like some others, had to tweak a value by a couple of px, that is what the height -= 2 is for.
Basically the main difference is that in MooTools, the delta info comes from event.wheel instead of an extra parameter passed to the event.
Also, I had problems if I bound this code to anything (event.target.scrollHeight for a bound function does not equal this.scrollHeight for a non-bound one)
Hope this helps someone as much as this post helped me ;)
Check out Leland Kwong's code.
Basic idea is to bind the wheeling event to the child element, and then use the native javascript property scrollHeight and the jquery property outerHeight of the child element to detect the end of the scroll, upon which return false to the wheeling event to prevent any scrolling.
var scrollableDist,curScrollPos,wheelEvent,dY;
$('#child-element').on('wheel', function(e){
scrollableDist = $(this)[0].scrollHeight - $(this).outerHeight();
curScrollPos = $(this).scrollTop();
wheelEvent = e.originalEvent;
dY = wheelEvent.deltaY;
if ((dY>0 && curScrollPos >= scrollableDist) ||
(dY<0 && curScrollPos <= 0)) {
return false;
}
});
I yoinked this from the chosen library: https://github.com/harvesthq/chosen/blob/master/coffee/chosen.jquery.coffee
function preventParentScroll(evt) {
var delta = evt.deltaY || -evt.wheelDelta || (evt && evt.detail)
if (delta) {
evt.preventDefault()
if (evt.type == 'DOMMouseScroll') {
delta = delta * 40
}
fakeTable.scrollTop = delta + fakeTable.scrollTop
}
}
var el = document.getElementById('some-id')
el.addEventListener('mousewheel', preventParentScroll)
el.addEventListener('DOMMouseScroll', preventParentScroll)
This works for me.
jQuery plugin with emulate natural scrolling for Internet Explorer
$.fn.mousewheelStopPropagation = function(options) {
options = $.extend({
// defaults
wheelstop: null // Function
}, options);
// Compatibilities
var isMsIE = ('Microsoft Internet Explorer' === navigator.appName);
var docElt = document.documentElement,
mousewheelEventName = 'mousewheel';
if('onmousewheel' in docElt) {
mousewheelEventName = 'mousewheel';
} else if('onwheel' in docElt) {
mousewheelEventName = 'wheel';
} else if('DOMMouseScroll' in docElt) {
mousewheelEventName = 'DOMMouseScroll';
}
if(!mousewheelEventName) { return this; }
function mousewheelPrevent(event) {
event.preventDefault();
event.stopPropagation();
if('function' === typeof options.wheelstop) {
options.wheelstop(event);
}
}
return this.each(function() {
var _this = this,
$this = $(_this);
$this.on(mousewheelEventName, function(event) {
var origiEvent = event.originalEvent;
var scrollTop = _this.scrollTop,
scrollMax = _this.scrollHeight - $this.outerHeight(),
delta = -origiEvent.wheelDelta;
if(isNaN(delta)) {
delta = origiEvent.deltaY;
}
var scrollUp = delta < 0;
if((scrollUp && scrollTop <= 0) || (!scrollUp && scrollTop >= scrollMax)) {
mousewheelPrevent(event);
} else if(isMsIE) {
// Fix Internet Explorer and emulate natural scrolling
var animOpt = { duration:200, easing:'linear' };
if(scrollUp && -delta > scrollTop) {
$this.stop(true).animate({ scrollTop:0 }, animOpt);
mousewheelPrevent(event);
} else if(!scrollUp && delta > scrollMax - scrollTop) {
$this.stop(true).animate({ scrollTop:scrollMax }, animOpt);
mousewheelPrevent(event);
}
}
});
});
};
https://github.com/basselin/jquery-mousewheel-stop-propagation/blob/master/mousewheelStopPropagation.js
The best solution I could find was listening to the scroll event on the window and set the scrollTop to the previous scrollTop if the child div was visible.
prevScrollPos = 0
$(window).scroll (ev) ->
if $('#mydiv').is(':visible')
document.body.scrollTop = prevScrollPos
else
prevScrollPos = document.body.scrollTop
There is a flicker in the background of the child div if you fire a lot of scroll events, so this could be tweaked, but it is hardly noticed and it was sufficient for my use case.
Don't use overflow: hidden; on body. It automatically scrolls everything to the top. There's no need for JavaScript either. Make use of overflow: auto;:
HTML Structure
<div class="overlay">
<div class="overlay-content"></div>
</div>
<div class="background-content">
lengthy content here
</div>
Styling
.overlay{
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(0, 0, 0, 0.8);
.overlay-content {
height: 100%;
overflow: scroll;
}
}
.background-content{
height: 100%;
overflow: auto;
}
Play with the demo here.
There's also a funny trick to lock the parent's scrollTop when mouse hovers over a scrollable element. This way you don't have to implement your own wheel scrolling.
Here's an example for preventing document scroll, but it can be adjusted for any element.
scrollable.mouseenter(function ()
{
var scroll = $(document).scrollTop();
$(document).on('scroll.trap', function ()
{
if ($(document).scrollTop() != scroll) $(document).scrollTop(scroll);
});
});
scrollable.mouseleave(function ()
{
$(document).off('scroll.trap');
});
M.K. offered a great plugin in his answer. Plugin can be found here. However, for the sake of completion, I thought it'd be a good idea to put it together in one answer for AngularJS.
Start by injecting the bower or npm (whichever is preferred)
bower install jquery-scrollLock --save
npm install jquery-scroll-lock --save
Add the following directive. I am choosing to add it as an attribute
(function() {
'use strict';
angular
.module('app')
.directive('isolateScrolling', isolateScrolling);
function isolateScrolling() {
return {
restrict: 'A',
link: function(sc, elem, attrs) {
$('.scroll-container').scrollLock();
}
}
}
})();
And the important piece the plugin fails to document in their website is the HTML structure that it must follow.
<div class="scroll-container locked">
<div class="scrollable" isolate-scrolling>
... whatever ...
</div>
</div>
The attribute isolate-scrolling must contain the scrollable class and it all needs to be inside the scroll-container class or whatever class you choose and the locked class must be cascaded.
It is worth to mention that with modern frameworks like reactJS, AngularJS, VueJS, etc, there are easy solutions for this problem, when dealing with fixed position elements. Examples are side panels or overlaid elements.
The technique is called a "Portal", which means that one of the components used in the app, without the need to actually extract it from where you are using it, will mount its children at the bottom of the body element, outside of the parent you are trying to avoid scrolling.
Note that it will not avoid scrolling the body element itself. You can combine this technique and mounting your app in a scrolling div to achieve the expected result.
Example Portal implementation in React's material-ui: https://material-ui-next.com/api/portal/
There is ES 6 crossbrowser + mobile vanila js decision:
function stopParentScroll(selector) {
let last_touch;
let MouseWheelHandler = (e, selector) => {
let delta;
if(e.deltaY)
delta = e.deltaY;
else if(e.wheelDelta)
delta = e.wheelDelta;
else if(e.changedTouches){
if(!last_touch){
last_touch = e.changedTouches[0].clientY;
}
else{
if(e.changedTouches[0].clientY > last_touch){
delta = -1;
}
else{
delta = 1;
}
}
}
let prevent = function() {
e.stopPropagation();
e.preventDefault();
e.returnValue = false;
return false;
};
if(selector.scrollTop === 0 && delta < 0){
return prevent();
}
else if(selector.scrollTop === (selector.scrollHeight - selector.clientHeight) && delta > 0){
return prevent();
}
};
selector.onwheel = e => {MouseWheelHandler(e, selector)};
selector.onmousewheel = e => {MouseWheelHandler(e, selector)};
selector.ontouchmove = e => {MouseWheelHandler(e, selector)};
}
I was searching for this for MooTools and this was the first that came up.
The original MooTools example would work with scrolling up, but not scrolling down so I decided to write this one.
MooTools 1.4.5: http://jsfiddle.net/3MzFJ/
MooTools 1.3.2: http://jsfiddle.net/VhnD4/
MooTools 1.2.6: http://jsfiddle.net/xWrw4/
var stopScroll = function (e) {
var scrollTo = null;
if (e.event.type === 'mousewheel') {
scrollTo = (e.event.wheelDelta * -1);
} else if (e.event.type === 'DOMMouseScroll') {
scrollTo = 40 * e.event.detail;
}
if (scrollTo) {
e.preventDefault();
this.scrollTo(0, scrollTo + this.scrollTop);
}
return false;
};
Usage:
(function)($){
window.addEvent('domready', function(){
$$('.scrollable').addEvents({
'mousewheel': stopScroll,
'DOMMouseScroll': stopScroll
});
});
})(document.id);
Just wondering if anyone has an idea as to how I might re-create a nav bar style that I saw a while ago, I just found the site I saw it on, but am not sure how they might have gotten there. Basically want it to scroll with the page then lock to the top...
http://lesscss.org/
Just do a quick "view source" on http://lesscss.org/ and you'll see this:
window.onscroll = function () {
if (!docked && (menu.offsetTop - scrollTop() < 0)) {
menu.style.top = 0;
menu.style.position = 'fixed';
menu.className = 'docked';
docked = true;
} else if (docked && scrollTop() <= init) {
menu.style.position = 'absolute';
menu.style.top = init + 'px';
menu.className = menu.className.replace('docked', '');
docked = false;
}
};
They're binding to the onscroll event for the window, this event is triggered when the window scrolls. The docked flag is set to true when the menu is "locked" to the top of the page, the menu is set to position:fixed at the same time that that flag is set to true. The rest is just some simple "are we about to scroll the menu off the page" and "are we about back where we started" position checking logic.
You have to be careful with onscroll events though, they can fire a lot in rapid succession so your handler needs to be pretty quick and should precompute as much as possible.
In jQuery, it would look pretty much the same:
$(window).scroll(function() {
// Pretty much the same as what's on lesscss.org
});
You see this sort of thing quite often with the "floating almost fixed position vertical toolbar" things such as those on cracked.com.
mu is too short answer is working, I'm just posting this to give you the jquery script!
var docked = false;
var menu = $('#menu');
var init = menu.offset().top;
$(window).scroll(function()
{
if (!docked && (menu.offset().top - $("body").scrollTop() < 0))
{
menu.css({
position : "fixed",
top: 0,
});
docked = true;
}
else if(docked && $("body").scrollTop() <= init)
{
menu.css({
position : "absolute",
top: init + 'px',
});
docked = false;
}
});
Mu's answer got me far. I tried my luck with replicationg lesscss.org's approach but ran into issues on browser resizing and zooming. Took me a while to find out how to react to that properly and how to reset the initial position (init) without jQuery or any other library.
Find a preview on JSFiddle: http://jsfiddle.net/ctietze/zeasg/
So here's the plain JavaScript code in detail, just in case JSFiddle refuses to work.
Reusable scroll-then-snap menu class
Here's a reusable version. I put the scrolling checks into a class because the helper methods involved cluttered my main namespace:
var windowScrollTop = function () {
return window.pageYOffset;
};
var Menu = (function (scrollOffset) {
var Menu = function () {
this.element = document.getElementById('nav');
this.docked = false;
this.initialOffsetTop = 0;
this.resetInitialOffsetTop();
}
Menu.prototype = {
offsetTop: function () {
return this.element.offsetTop;
},
resetInitialOffsetTop: function () {
this.initialOffsetTop = this.offsetTop();
},
dock: function () {
this.element.className = 'docked';
this.docked = true;
},
undock: function () {
this.element.className = this.element.className.replace('docked', '');
this.docked = false;
},
toggleDock: function () {
if (this.docked === false && (this.offsetTop() - scrollOffset() < 0)) {
this.dock();
} else if (this.docked === true && (scrollOffset() <= this.initialOffsetTop)) {
this.undock();
}
}
};
return Menu;
})(windowScrollTop);
var menu = new Menu();
window.onscroll = function () {
menu.toggleDock();
};
Handle zoom/page resize events
var updateMenuTop = function () {
// Shortly dock to reset the initial Y-offset
menu.undock();
menu.resetInitialOffsetTop();
// If appropriate, undock again based on the new value
menu.toggleDock();
};
var zoomListeners = [updateMenuTop];
(function(){
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0];
var lastWidth = 0;
function pollZoomFireEvent() {
var widthNow = w.innerWidth || e.clientWidth || g.clientWidth;
if (lastWidth == widthNow) {
return;
}
lastWidth = widthNow;
// Length changed, user must have zoomed, invoke listeners.
for (i = zoomListeners.length - 1; i >= 0; --i) {
zoomListeners[i]();
}
}
setInterval(pollZoomFireEvent, 100);
})();
Sounds like an application of Jquery ScrollTop and some manipulation of CSS properties of the navbar element. So for example, under certain scroll conditions the navbar element is changed from absolute positioning with calculated co-ordinates to fixed positioning.
http://api.jquery.com/scrollTop/
The effect you describe would usually start with some type of animation, like in TheDeveloper's answer. Default animations typically slide an element around by changing its position over time or fade an element in/out by changing its opacity, etc.
Getting the "bouce back" or "snap to" effect usually involves easing. All major frameworks have some form of easing available. It's all about personal preference; you can't really go wrong with any of them.
jQuery has easing plugins that you could use with the .animate() function, or you can use jQueryUI.
MooTools has easing built in to the FX class of the core library.
Yahoo's YUI also has easing built in.
If you can remember what site it was, you could always visit it again and take a look at their source to see what framework and effect was used.