Animate scrollTop not working in firefox
JqueryFirefoxScrolltopJquery Problem Overview
This function works fine. It scrolls the body to a desired container's offset
function scrolear(destino){
var stop = $(destino).offset().top;
var delay = 1000;
$('body').animate({scrollTop: stop}, delay);
return false;
}
But not in Firefox. Why?
-EDIT-
To handle de double trigger in the accepted answer, I suggest stoping the element before the animation:
$('body,html').stop(true,true).animate({scrollTop: stop}, delay);
Jquery Solutions
Solution 1 - Jquery
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( ... );
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');
}));
Solution 2 - Jquery
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
});
Solution 3 - Jquery
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.
Solution 4 - Jquery
You might want to dodge the issue by using a plugin – more specifically, [my plugin][1] :)
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][2], - feature-testing the actual browser behaviour is tricky to get right (see [my comments on @Stephen's answer][3]),
- but most importantly, there is a [whole bunch of usability problems][4] which you'd want to deal with for a decent user experience.
I'm obviously biased, but [jQuery.scrollable][1] 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][5].
All of which would leave you with
function scrolear ( destino ) {
var $window = $( window ),
targetPosition = getScrollTargetPosition ( $( destino ), $window );
$window.scrollTo( targetPosition, { duration: 1000 } );
return false;
}
[1]: https://github.com/hashchange/jquery.scrollable "jQuery.scrollable - Github" [2]: https://stackoverflow.com/questions/8790752/callback-of-animate-gets-called-twice-jquery/8791175#comment48499212_8791175 "Callback of .animate() gets called twice jquery - Stack Overflow" [3]: https://stackoverflow.com/questions/8149155/animate-scrolltop-not-working-in-firefox/21583714#comment46979441_21583714 "Animate scrollTop not working in firefox - comment by @hashchange" [4]: https://stackoverflow.com/a/32046714/508355 "jQuery scroll to element - Stack Overflow" [5]: https://gist.github.com/hashchange/c368ce9c642c1a70edda "hashchange/scrollTargetPosition.js - Github Gist"
Solution 5 - Jquery
Beware of this. I had the same problem, neither Firefox or Explorer scrolling with
$('body').animate({scrollTop:pos_},1500,function(){do X});
So I used like David said
$('body, html').animate({scrollTop:pos_},1500,function(){do X});
Great it worked, but new problem, since there are two elements, body and html, function is executed twice, this is, do X runs two times.
tried only with 'html', and Firefox and Explorer work, but now Chrome does not support this.
So needed body for Chrome, and html for Firefox and Explorer. Is it a jQuery bug? don't know.
Just beware of your function, since it will run twice.
Solution 6 - Jquery
I would recommend not relying on body
nor html
as a more portable solution. Just add a div in the body that aims to contain the scrolled elements and style it like to enable full-size scrolling:
#my-scroll {
position: absolute;
width: 100%;
height: 100%;
overflow: auto;
}
(assuming that display:block;
and top:0;left:0;
are defaults that matches your goal), then use $('#my-scroll')
for your animations.
Solution 7 - Jquery
This is the real deal. It works on Chrome and Firefox flawlessly. It is even sad that some ignorant vote me down. This code literally works perfectly as is on all browsers. You only need to add a link and put the id of the element you want to scroll in the href and it works without specifying anything. Pure reusable and reliable code.
$(document).ready(function() {
function filterPath(string) {
return string
.replace(/^\//,'')
.replace(/(index|default).[a-zA-Z]{3,4}$/,'')
.replace(/\/$/,'');
}
var locationPath = filterPath(location.pathname);
var scrollElem = scrollableElement('html', 'body');
$('a[href*=#]').each(function() {
var thisPath = filterPath(this.pathname) || locationPath;
if (locationPath == thisPath
&& (location.hostname == this.hostname || !this.hostname)
&& this.hash.replace(/#/,'') ) {
var $target = $(this.hash), target = this.hash;
if (target) {
var targetOffset = $target.offset().top;
$(this).click(function(event) {
event.preventDefault();
$(scrollElem).animate({scrollTop: targetOffset}, 400, function() {
location.hash = target;
});
});
}
}
});
// use the first element that is "scrollable"
function scrollableElement(els) {
for (var i = 0, argLength = arguments.length; i <argLength; i++) {
var el = arguments[i],
$scrollElement = $(el);
if ($scrollElement.scrollTop()> 0) {
return el;
} else {
$scrollElement.scrollTop(1);
var isScrollable = $scrollElement.scrollTop()> 0;
$scrollElement.scrollTop(0);
if (isScrollable) {
return el;
}
}
}
return [];
}
});
Solution 8 - Jquery
I encountered the same problem just recently and I solved it by doing this:
$ ('html, body'). animate ({scrollTop: $ ('. class_of_div'). offset () .top}, 'fast'});
And youpi !!! it works on all browsers.
in case the positioning is not correct, you can subtract a value from the offset () .top by doing this
$ ('html, body'). animate ({scrollTop: $ ('. class_of_div'). offset () .top-desired_value}, 'fast'});
Solution 9 - Jquery
For me the problem was that firefox automatically jumped to the anchor with the name-attribute the same as the hash name I put into the URL. Even though I put .preventDefault() to prevent that. So after changing the name attributes, firefox did not automatically jump to the anchors, but perform the animation right.
@Toni Sorry if this wasn't understandable. The thing is I changed the hashes in the URL like www.someurl.com/#hashname. Then I had for example an anchor like <a name="hashname" ...></a>
to which jQuery should scroll to automatically. But it didn't because it jumped right to the anchor with the matching name attribute in Firefox without any scroll animation. Once I changed the name attribute to something different from the hash name, for example to name="hashname-anchor"
, the scrolling worked.
Solution 10 - Jquery
For me, it was avoiding appending the ID at the point of animation:
Avoiding:
scrollTop: $('#' + id).offset().top
Preparing the id beforehand and doing this instead:
scrollTop: $(id).offset().top
Fixed in FF. (The css additions didn't make a difference for me)
Solution 11 - Jquery
setTimeout(function(){
$('html,body').animate({ scrollTop: top }, 400);
},0);
Hope this works.