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