Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 and html 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;
}

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.