Animate scrollTop not working in firefox
Asked Answered
H

11

164

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);
Hagbut answered 16/11, 2011 at 9:13 Comment(0)
I
331

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');
}));
Indiscerptible answered 16/11, 2011 at 9:18 Comment(11)
JS solution has a flaw! animate function is executed twice, for html element and for body element respectively. It looks like Webkit browsers use body and the rest uses html. This fix should do the work $(jQuery.browser.webkit ? "body": "html").animate(...);Fearnought
I found that the solution from Andreyco didn't work in jQuery < 1.4 which I was using. I was able to fix it like this: $(jQuery.browser.mozilla ? "html" : "body").animate(...);. It would be great to not use browse sniffing, but feature detection. Not sure how to do feature detection on the CSS thoughHeedless
Note that the two solutions don't work together. For post jQ 1.9, use Modernizer: #6580401Nock
Note, jQuery.browser is deprecated and missing from the latest jQuery versionLanilaniard
If overflow auto|hidden are not doing it, simply use visible and it will force it (just a note for anyone else having issues with it)Ivy
4 years on, and this answer is still proving useful. Thanks very much!Penner
visible solves the problem in firefox. But then the scroll is NOT working !Alphitomancy
If someone is trying to use a reference element instead of pixels directly, you will have trouble using the conventional code $(referencia).offset().top. Instead, use: $(referencia).offset().top + $('body').get(0).scrollTop + $('html').get(0).scrollTop. But, use this only in Firefox, that is, use a condition of type if (typeof InstallTrigger! == 'undefined')Alpheus
Why not using "window.scroll()" instead ? It's less invasive than the css solution, and it doesn't depends on webkit / mozilla...Norwegian
@DamFa: Mainly, because window.scroll doesn't animate. You could of course roll your own thing with intervals and scrollBy. If you want to add easing to that, you suddenly have a lot of code to write for a rather minor aspect of your product.Indiscerptible
We can use native js -(navigator.userAgent.toLowerCase().indexOf('firefox') > -1) && (navigator.appName == "Netscape") to check Firefox browser as jQuery.browser is now totally deprecatedKendakendal
C
19

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
});
Cobalt answered 5/2, 2014 at 17:4 Comment(5)
Works great under normal circumstances, thanks! There are a couple of gotchas to be aware of, though. (1) If the body does not have enough content to overflow the window when the feature test runs, the window won't scroll and the test returns a bogus "body" result. (2) If the test runs inside an iframe in iOS, it fails for the same reason. Iframes in iOS expand vertically (not horizontally) until the content fits inside without scrolling. (3) Because the test is executed in the main window, it triggers scroll handlers which are already in place. ... (continued)Carlist
... For these reasons, a bulletproof test would have to run inside a dedicated iframe (solves (3)) with large-enough content (solves (1)) which is tested for horizontal scroll (solves (2)).Carlist
I actually had to run this function in a timeout script to make this give consistent results, otherwise it seemed to sometimes return html and sometimes return body. Here's my code after declaring the function var scrollTopElement; setTimeout( function() { scrollTopElement = getScrollTopElement(); console.log(scrollTopElement); }, 1000); Thank you for this though, it has solved my problem.Coenurus
Never mind, I figured out what the actual issue is. If you're at the bottom of the page already when you reload (f5), this script will return html instead of body like it's supposed. This is only if you're scrolled all the way down the webpage and you reload, otherwise it works.Coenurus
This worked for me after I added a check for if the page opens at the bottom.Sacking
A
6

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.

Acquirement answered 2/3, 2014 at 19:53 Comment(0)
C
3

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:

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;
}
Carlist answered 5/11, 2015 at 23:37 Comment(0)
I
1

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.

Incompliant answered 28/5, 2013 at 2:24 Comment(2)
did u find a workaround for this? And since jquery browser detection is deprecated, I dont know how can I use feature detection for distinguishing between chrome and firefox. Their documentation doesnt specify a feature which is present in chrome and not in firefox or viceversa. :(Monolith
what about .stop(true,true).animate ?Hagbut
V
0

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.

Veron answered 18/2, 2015 at 15:50 Comment(0)
B
0

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 [];
  }
});
Bogoch answered 7/3, 2015 at 4:37 Comment(1)
It might be complex, but it is really useful. It basically works Plug-N-Play in all browsers. A simpler solution might not allow you to do that.Bogoch
B
0

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'});
Barnacle answered 16/10, 2018 at 14:58 Comment(2)
This solution is already mentioned in the existing answers but thanks for sharingHagbut
This method still encounters a glitch in Firefox, as noted in the question I just posted: https://mcmap.net/q/151640/-scrolltop-with-jquery-offset-not-working-properly-in-firefox-unless-mouseout-occurs/1056713Hypocaust
R
-3

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.

Receptacle answered 20/5, 2013 at 13:33 Comment(0)
C
-5

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)

Compose answered 15/5, 2013 at 6:56 Comment(0)
O
-8
setTimeout(function(){                               
                   $('html,body').animate({ scrollTop: top }, 400);
                 },0);

Hope this works.

Oared answered 14/5, 2013 at 13:21 Comment(3)
How does the setTimeut help, here?Hagbut
@ToniMichelCaubet presumably the goal is to make the animation asynchronous. jQuery animations are already asynchronous, though, so this accomplishes nothing.Minuteman
Not a thoughtful answer to the problem. Not sure what this will accomplish.Eurythermal

© 2022 - 2024 — McMap. All rights reserved.