Animate scrollTop not working in firefox

JqueryFirefoxScrolltop

Jquery 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( ... );

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

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 and html [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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionToni Michel CaubetView Question on Stackoverflow
Solution 1 - JqueryDavid HedlundView Answer on Stackoverflow
Solution 2 - JqueryStephenView Answer on Stackoverflow
Solution 3 - JqueryAidan EwenView Answer on Stackoverflow
Solution 4 - JqueryhashchangeView Answer on Stackoverflow
Solution 5 - JqueryAvenida GezView Answer on Stackoverflow
Solution 6 - JqueryJavaromeView Answer on Stackoverflow
Solution 7 - JquerydrjorgepolancoView Answer on Stackoverflow
Solution 8 - Jqueryuser9594980View Answer on Stackoverflow
Solution 9 - JqueryPeterView Answer on Stackoverflow
Solution 10 - JquerybcmView Answer on Stackoverflow
Solution 11 - JquerySimbuView Answer on Stackoverflow