Animieren Sie scrollTop, das in Firefox nicht funktioniert

164

Diese Funktion funktioniert gut. Es rollt den Körper zum Versatz eines gewünschten Containers

function scrolear(destino){
    var stop = $(destino).offset().top;
    var delay = 1000;
    $('body').animate({scrollTop: stop}, delay);
    return false;
}

Aber nicht in Firefox. Warum?

-BEARBEITEN-

Um den doppelten Auslöser in der akzeptierten Antwort zu behandeln, empfehle ich, das Element vor der Animation anzuhalten:

$('body,html').stop(true,true).animate({scrollTop: stop}, delay);
Toni Michel Caubet
quelle
2
Ihr endgültiger Code ist der eleganteste aller Dinge hier.
Lizardx

Antworten:

331

Firefox platziert den Überlauf auf der htmlEbene, sofern er nicht speziell für ein anderes Verhalten ausgelegt ist.

Verwenden Sie, um es in Firefox zum Laufen zu bringen

$('body,html').animate( ... );

Arbeitsbeispiel

Die CSS-Lösung besteht darin, die folgenden Stile festzulegen:

html { overflow: hidden; height: 100%; }
body { overflow: auto; height: 100%; }

Ich würde annehmen, dass die JS-Lösung am wenigsten invasiv wäre.


Aktualisieren

Ein Großteil der folgenden Diskussion konzentriert sich auf die Tatsache, dass das Animieren scrollTopvon zwei Elementen dazu führen würde, dass der Rückruf zweimal aufgerufen wird. Browser-Erkennungsfunktionen wurden vorgeschlagen und anschließend veraltet, und einige sind wohl ziemlich weit hergeholt.

Wenn der Rückruf idempotent ist und nicht viel Rechenleistung erfordert, ist das zweimalige Auslösen möglicherweise kein Problem. Wenn mehrere Aufrufe des Rückrufs wirklich ein Problem darstellen und Sie die Funktionserkennung vermeiden möchten, ist es möglicherweise einfacher zu erzwingen, dass der Rückruf nur einmal innerhalb des Rückrufs ausgeführt wird:

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');
}));
David Hedlund
quelle
51
JS-Lösung hat einen Fehler! Die Animationsfunktion wird zweimal ausgeführt, sowohl für das HTML- Element als auch für das Body- Element. Es sieht so aus, als ob Webkit-Browser body verwenden und der Rest HTML . Dieser Fix sollte die Arbeit $(jQuery.browser.webkit ? "body": "html").animate(...);
erledigen
2
Ich stellte fest, dass die Lösung von Andreyco in jQuery <1.4, das ich verwendete, nicht funktionierte. Ich konnte es so beheben : $(jQuery.browser.mozilla ? "html" : "body").animate(...);. Es wäre großartig, nicht das Durchsuchen von Sniffing zu verwenden, sondern die Funktionserkennung. Ich bin mir nicht sicher, wie ich die Feature-Erkennung im CSS durchführen soll
Rustavore,
23
Hinweis, jQuery.browserist veraltet und fehlt in der neuesten jQuery-Version
spiderplant0
2
4 Jahre später, und diese Antwort erweist sich immer noch als nützlich. Vielen Dank!
Matt
1
@ DamFa: Hauptsächlich, weil window.scrollnicht animiert. Sie könnten natürlich Ihr eigenes Ding mit Intervallen und rollen scrollBy. Wenn Sie das noch vereinfachen möchten, müssen Sie plötzlich viel Code für einen eher kleinen Aspekt Ihres Produkts schreiben.
David Hedlund
19

Das Erkennen von Funktionen und das anschließende Animieren eines einzelnen unterstützten Objekts wäre nett, aber es gibt keine einzeilige Lösung. In der Zwischenzeit können Sie hier ein Versprechen verwenden, um einen einzelnen Rückruf pro Ausführung durchzuführen.

$('html, body')
    .animate({ scrollTop: 100 })
    .promise()
    .then(function(){
        // callback code here
    })
});

UPDATE: So können Sie stattdessen die Feature-Erkennung verwenden. Dieser Codeabschnitt muss vor Ihrem Animationsaufruf ausgewertet werden:

// 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();

Verwenden Sie nun die Variable, die wir gerade als Selektor für die Bildlaufanimation definiert haben, und verwenden Sie die reguläre Syntax:

$(scrollTopElement).animate({ scrollTop: 100 }, 500, function() {
    // normal callback
});
Stephen
quelle
Funktioniert unter normalen Umständen hervorragend, danke! Es gibt jedoch ein paar Fallstricke, die Sie beachten sollten. (1) Wenn der Textkörper nicht über genügend Inhalt verfügt, um das Fenster beim Ausführen des Funktionstests zu überlaufen, wird das Fenster nicht gescrollt und der Test gibt ein falsches "Körper" -Ergebnis zurück. (2) Wenn der Test in einem Iframe unter iOS ausgeführt wird, schlägt er aus demselben Grund fehl. Iframes in iOS werden vertikal (nicht horizontal) erweitert, bis der Inhalt ohne Scrollen hineinpasst. (3) Da der Test im Hauptfenster ausgeführt wird, werden bereits vorhandene Scroll-Handler ausgelöst. ... (Fortsetzung)
Hashchange
... Aus diesen Gründen müsste ein kugelsicherer Test in einem dedizierten Iframe (Lösungen (3)) mit ausreichend großem Inhalt (Lösungen (1)) ausgeführt werden, der auf horizontales Scrollen (Lösungen (2)) getestet wird.
Hashchange
Ich musste diese Funktion tatsächlich in einem Timeout-Skript ausführen, damit dies zu konsistenten Ergebnissen führte. Andernfalls schien sie manchmal zurückzukehren htmlund manchmal zurückzukehren body. Hier ist mein Code, nachdem ich die Funktion deklariert habe. var scrollTopElement; setTimeout( function() { scrollTopElement = getScrollTopElement(); console.log(scrollTopElement); }, 1000); Vielen Dank dafür, er hat mein Problem gelöst.
Tony M
Egal, ich habe herausgefunden, was das eigentliche Problem ist. Wenn Sie sich bereits beim Neuladen am Ende der Seite befinden (f5), wird dieses Skript zurückgegeben, htmlanstatt bodywie angenommen. Dies ist nur möglich, wenn Sie bis zum Ende der Webseite gescrollt und neu geladen haben. Andernfalls funktioniert es.
Tony M
Dies funktionierte für mich, nachdem ich überprüft hatte, ob die Seite unten geöffnet wird.
Scott
6

Ich habe ewig versucht herauszufinden, warum mein Code nicht funktioniert -

$('body,html').animate({scrollTop: 50}, 500);

Das Problem war in meinem CSS -

body { height: 100%};

Ich habe es autostattdessen eingestellt (und war besorgt darüber, warum es überhaupt eingestellt wurde 100%). Das hat es für mich behoben.

Aidan Ewen
quelle
2

Vielleicht möchten Sie dem Problem mit einem Plugin ausweichen - genauer gesagt meinem Plugin :)

Im Ernst, obwohl das Grundproblem längst behoben ist (verschiedene Browser verwenden unterschiedliche Elemente für das Scrollen im Fenster), gibt es auf der ganzen Linie einige nicht triviale Probleme, die Sie stören können:

Ich bin offensichtlich voreingenommen, aber jQuery.scrollable ist tatsächlich eine gute Wahl, um diese Probleme anzugehen. (Tatsächlich kenne ich kein anderes Plugin, das sie alle handhabt.)

Darüber hinaus können Sie die Zielposition - die Position, zu der Sie scrollen - mit der getScrollTargetPosition()Funktion in diesem Kern kugelsicher berechnen .

All das würde dich verlassen

function scrolear ( destino ) {
    var $window = $( window ),
        targetPosition = getScrollTargetPosition ( $( destino ), $window );

    $window.scrollTo( targetPosition, { duration: 1000 } );

    return false;
}
Hashwechsel
quelle
1

Vorsicht davor. Ich hatte das gleiche Problem, weder Firefox noch Explorer scrollen mit

$('body').animate({scrollTop:pos_},1500,function(){do X});

Also habe ich verwendet, wie David sagte

$('body, html').animate({scrollTop:pos_},1500,function(){do X});

Großartig, es hat funktioniert, aber neues Problem, da es zwei Elemente gibt, body und html, wird die Funktion zweimal ausgeführt, dh do X wird zweimal ausgeführt.

versuchte es nur mit 'html' und Firefox und Explorer funktionieren, aber jetzt unterstützt Chrome dies nicht.

Also brauchte man Body für Chrome und HTML für Firefox und Explorer. Ist es ein jQuery-Fehler? Ich weiß es nicht.

Passen Sie einfach auf Ihre Funktion auf, da sie zweimal ausgeführt wird.

Avenida Gez
quelle
Hast du eine Problemumgehung dafür gefunden? Und da die Erkennung von JQuery-Browsern veraltet ist, weiß ich nicht, wie ich die Funktionserkennung zur Unterscheidung zwischen Chrome und Firefox verwenden kann. In der Dokumentation ist keine Funktion angegeben, die in Chrome und nicht in Firefox oder umgekehrt vorhanden ist. :(
Mandeep Jain
2
Was ist mit .stop (wahr, wahr) .animate?
Toni Michel Caubet
0

Ich würde empfehlen , nicht auf Berufung bodynoch htmlals eine tragbare Lösung. Fügen Sie einfach ein Div in den Body ein, das die gescrollten Elemente enthalten soll, und gestalten Sie es so, dass das Scrollen in voller Größe ermöglicht wird:

#my-scroll {
  position: absolute;
  width: 100%;
  height: 100%;
  overflow: auto;
}

(unter der Annahme , dass display:block;und top:0;left:0;sind Standardwerte , die Ihr Ziel übereinstimmt), dann verwenden Sie $('#my-scroll')für Ihre Animationen.

Javarome
quelle
0

Das ist der wahre Deal. Es funktioniert einwandfrei auf Chrome und Firefox. Es ist sogar traurig, dass mich einige Unwissende ablehnen. Dieser Code funktioniert buchstäblich perfekt wie in allen Browsern. Sie müssen nur einen Link hinzufügen und die ID des Elements, in das Sie scrollen möchten, in die href einfügen. Dies funktioniert ohne Angabe von Angaben. Rein wiederverwendbarer und zuverlässiger 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 [];
  }
});
drjorgepolanco
quelle
1
Es mag komplex sein, aber es ist wirklich nützlich. Grundsätzlich funktioniert Plug-N-Play in allen Browsern. Eine einfachere Lösung erlaubt Ihnen dies möglicherweise nicht.
Drjorgepolanco
0

Ich bin erst kürzlich auf dasselbe Problem gestoßen und habe es folgendermaßen gelöst:

$ ('html, body'). animate ({scrollTop: $ ('. class_of_div'). offset () .top}, 'fast'});

Und youpi !!! Es funktioniert in allen Browsern.

Falls die Positionierung nicht korrekt ist, können Sie auf diese Weise einen Wert vom Offset () .top abziehen

$ ('html, body'). animate ({scrollTop: $ ('. class_of_div'). offset () .top-desired_value}, 'fast'});
Balthazar DOSSOU
quelle
Diese Lösung ist bereits in den vorhandenen Antworten erwähnt, aber danke für das Teilen
Toni Michel Caubet
Diese Methode stößt immer noch auf einen Fehler in Firefox, wie in der Frage erwähnt, die ich gerade gepostet habe: stackoverflow.com/q/58617731/1056713
Chaya Cooper
-3

Für mich war das Problem, dass Firefox automatisch mit dem Namensattribut zum Anker sprang, der dem Hash-Namen entspricht, den ich in die URL eingegeben habe. Obwohl ich .preventDefault () gesetzt habe, um das zu verhindern. Nach dem Ändern der Namensattribute sprang Firefox nicht automatisch zu den Ankern, sondern führte die Animation richtig aus.

@ Toni Sorry wenn das nicht verständlich war. Die Sache ist, dass ich die Hashes in der URL wie www.someurl.com/#hashname geändert habe. Dann hatte ich zum Beispiel einen Anker, <a name="hashname" ...></a>zu dem jQuery automatisch scrollen sollte. Dies war jedoch nicht der Fall, da in Firefox ohne Bildlaufanimation direkt zum Anker mit dem übereinstimmenden Namensattribut gesprungen wurde. Nachdem ich das Namensattribut in etwas anderes als den Hashnamen geändert hatte, z. B. in name="hashname-anchor", funktionierte das Scrollen.

Peter
quelle
-5

Für mich wurde vermieden, die ID an der Stelle der Animation anzuhängen:

Vermeiden:

 scrollTop: $('#' + id).offset().top

Bereiten Sie die ID im Voraus vor und tun Sie dies stattdessen:

 scrollTop: $(id).offset().top

In FF behoben. (Die CSS-Ergänzungen haben für mich keinen Unterschied gemacht)

bcm
quelle
-8
setTimeout(function(){                               
                   $('html,body').animate({ scrollTop: top }, 400);
                 },0);

Hoffe das funktioniert.

Simbu
quelle
4
Wie hilft das setTimeut hier?
Toni Michel Caubet
2
@ToniMichelCaubet vermutlich ist das Ziel, die Animation asynchron zu machen. jQuery-Animationen sind jedoch bereits asynchron, sodass dadurch nichts erreicht wird.
MWCZ
Keine nachdenkliche Antwort auf das Problem. Ich bin mir nicht sicher, was dies bewirken wird.
Erier