Animate scrollTop not working in firefox
Firefox places the overflow at the html
level, unless specifically styled to behave differently.
To get it to work in Firefox, use
$('body,html').animate( ... );
Working example
The CSS solution would be to set the following styles:
html { overflow: hidden; height: 100%; }
body { overflow: auto; height: 100%; }
I would assume that the JS solution would be least invasive.
Update
A lot of the discussion below focuses on the fact that animating the scrollTop
of two elements would cause the callback to be invoked twice. Browser-detection features have been suggested and subsequently deprecated, and some are arguably rather far-fetched.
If the callback is idempotent and doesn't require a lot of computing power, firing it twice may be a complete non-issue. If multiple invocations of the callback are truly an issue, and if you want to avoid feature-detection, it might be more straight-forward to enforce that the callback is only run once from within the callback:
function runOnce(fn) {
var count = 0;
return function() {
if(++count == 1)
fn.apply(this, arguments);
};
};
$('body, html').animate({ scrollTop: stop }, delay, runOnce(function() {
console.log('scroll complete');
}));
Feature detection and then animating on a single supported object would be nice, but there's not a one line solution. In the meantime, here's a way to use a promise to do a single callback per execution.
$('html, body')
.animate({ scrollTop: 100 })
.promise()
.then(function(){
// callback code here
})
});
UPDATE: Here's how you could use feature detection instead. This chunk of code needs to get evaluated before your animation call:
// Note that the DOM needs to be loaded first,
// or else document.body will be undefined
function getScrollTopElement() {
// if missing doctype (quirks mode) then will always use 'body'
if ( document.compatMode !== 'CSS1Compat' ) return 'body';
// if there's a doctype (and your page should)
// most browsers will support the scrollTop property on EITHER html OR body
// we'll have to do a quick test to detect which one...
var html = document.documentElement;
var body = document.body;
// get our starting position.
// pageYOffset works for all browsers except IE8 and below
var startingY = window.pageYOffset || body.scrollTop || html.scrollTop;
// scroll the window down by 1px (scrollTo works in all browsers)
var newY = startingY + 1;
window.scrollTo(0, newY);
// And check which property changed
// FF and IE use only html. Safari uses only body.
// Chrome has values for both, but says
// body.scrollTop is deprecated when in Strict mode.,
// so let's check for html first.
var element = ( html.scrollTop === newY ) ? 'html' : 'body';
// now reset back to the starting position
window.scrollTo(0, startingY);
return element;
}
// store the element selector name in a global var -
// we'll use this as the selector for our page scrolling animation.
scrollTopElement = getScrollTopElement();
Now use the var that we just defined as the selector for the page scrolling animation, and use the regular syntax:
$(scrollTopElement).animate({ scrollTop: 100 }, 500, function() {
// normal callback
});
I spent ages trying to work out why my code wouldn't work -
$('body,html').animate({scrollTop: 50}, 500);
The problem was in my css -
body { height: 100%};
I set it to auto
instead (and was left worrying about why it was set to 100%
in the first place). That fixed it for me.
You might want to dodge the issue by using a plugin – more specifically, my plugin :)
Seriously, even though the basic problem has long since been addressed (different browsers use different elements for window scrolling), there are quite a few non-trivial issues down the line which can trip you up:
- Simply animating both
body
andhtml
has its problems, - feature-testing the actual browser behaviour is tricky to get right (see my comments on @Stephen's answer),
- but most importantly, there is a whole bunch of usability problems which you'd want to deal with for a decent user experience.
I'm obviously biased, but jQuery.scrollable is actually a good choice to address these issues. (In fact, I don't know of any other plugin which handles them all.)
In addition, you can calculate the target position – the one which you scroll to – in a bullet-proof way with the getScrollTargetPosition()
function in this gist.
All of which would leave you with
function scrolear ( destino ) {
var $window = $( window ),
targetPosition = getScrollTargetPosition ( $( destino ), $window );
$window.scrollTo( targetPosition, { duration: 1000 } );
return false;
}