Animate scrollTop nie działa w przeglądarce Firefox


164

Ta funkcja działa dobrze. Przewija treść do żądanego przesunięcia kontenera

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

Ale nie w przeglądarce Firefox. Czemu?

-EDYTOWAĆ-

Aby obsłużyć de double trigger w zaakceptowanej odpowiedzi, proponuję zatrzymać element przed animacją:

$('body,html').stop(true,true).animate({scrollTop: stop}, delay);

2
Twój ostateczny kod jest najbardziej elegancki ze wszystkich tutaj.
— Lizardx

Odpowiedzi:


331

Firefox umieszcza przepełnienie na htmlpoziomie, chyba że ma specjalny styl, aby zachowywać się inaczej.

Aby to działało w przeglądarce Firefox, użyj

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

Przykład roboczy

Rozwiązaniem CSS byłoby ustawienie następujących stylów:

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

Przypuszczam, że rozwiązanie JS będzie najmniej inwazyjne.


Aktualizacja

Wiele poniższych dyskusji skupia się na fakcie, że animowanie scrollTopdwóch elementów spowodowałoby dwukrotne wywołanie wywołania zwrotnego. Zaproponowano, a następnie wycofano funkcje wykrywania przeglądarki, a niektóre z nich są prawdopodobnie dość naciągane.

Jeśli wywołanie zwrotne jest idempotentne i nie wymaga dużej mocy obliczeniowej, dwukrotne wystrzelenie go może całkowicie nie stanowić problemu. Jeśli wielokrotne wywołania wywołania zwrotnego są naprawdę problemem i jeśli chcesz uniknąć wykrywania funkcji, prostsze może być wymuszenie, aby wywołanie zwrotne było uruchamiane tylko raz z poziomu wywołania zwrotnego:

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

51
Rozwiązanie JS ma wadę! funkcja animate jest wykonywana dwukrotnie, odpowiednio dla elementu html i elementu body . Wygląda na to, że przeglądarki Webkit używają treści, a reszta korzysta z html . Ta poprawka powinna $(jQuery.browser.webkit ? "body": "html").animate(...);
— wystarczyć

2
Okazało się, że rozwiązanie Andreyco nie działa w jQuery <1.4, którego używałem. Udało mi się naprawić to tak: $(jQuery.browser.mozilla ? "html" : "body").animate(...);. Byłoby wspaniale nie używać węszenia przeglądania, ale wykrywania funkcji. Nie jestem jednak pewien, jak wykonać wykrywanie funkcji w CSS
— Rustavore

23
Uwaga: jQuery.browserjest przestarzały i brakuje go w najnowszej wersji jQuery
— spiderplant0

2
4 lata później ta odpowiedź jest nadal przydatna. Dziękuję bardzo!
— Matt

1
@DamFa: Głównie dlatego, że window.scrollnie animuje. Możesz oczywiście toczyć swoje własne rzeczy z przerwami i scrollBy. Jeśli chcesz dodać do tego łagodzenie, nagle masz dużo kodu do napisania dla raczej drugorzędnego aspektu produktu.
— David Hedlund

19

Wykrywanie funkcji, a następnie animowanie na pojedynczym obsługiwanym obiekcie byłoby fajne, ale nie ma rozwiązania opartego na jednej linii. W międzyczasie, oto sposób na użycie obietnicy do wykonania pojedynczego wywołania zwrotnego na wykonanie.

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

AKTUALIZACJA: Oto, jak można zamiast tego użyć wykrywania funkcji. Ten fragment kodu musi zostać oceniony przed wywołaniem animacji:

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

Teraz użyj zmiennej, którą właśnie zdefiniowaliśmy jako selektor animacji przewijania strony, i użyj zwykłej składni:

$(scrollTopElement).animate({ scrollTop: 100 }, 500, function() {
    // normal callback
});

Działa świetnie w normalnych okolicznościach, dzięki! Jest jednak kilka pułapek, o których należy pamiętać. (1) Jeśli treść nie ma wystarczającej ilości treści, aby przepełnić okno po uruchomieniu testu funkcji, okno nie będzie się przewijać, a test zwróci fałszywy wynik „treści”. (2) Jeśli test zostanie uruchomiony w elemencie iframe w iOS, kończy się niepowodzeniem z tego samego powodu. Ramki iframe w iOS rozwijają się w pionie (nie w poziomie), aż zawartość zmieści się w środku bez przewijania. (3) Ponieważ test jest wykonywany w oknie głównym, wyzwala programy obsługi przewijania, które są już na miejscu. ... (ciąg dalszy)
— hashchange

... Z tych powodów test kuloodporny musiałby przebiegać w dedykowanej ramce iframe (rozwiązuje (3)) o wystarczająco dużej zawartości (rozwiązuje (1)), która jest testowana pod kątem przewijania w poziomie (rozwiązuje (2)).
— hashchange

Właściwie musiałem uruchomić tę funkcję w skrypcie limitu czasu, aby uzyskać spójne wyniki, w przeciwnym razie wydawało się, że czasami wraca, htmla czasami wraca body. Oto mój kod po zadeklarowaniu funkcji var scrollTopElement; setTimeout( function() { scrollTopElement = getScrollTopElement(); console.log(scrollTopElement); }, 1000); Dziękuję za to, ale rozwiązał mój problem.
— Tony M

Nieważne, zorientowałem się, o co właściwie chodzi. Jeśli jesteś na dole strony już po ponownym załadowaniu (f5), ten skrypt zwróci htmlzamiast bodytak, jak powinien. Dzieje się tak tylko wtedy, gdy przewiniesz całą stronę w dół i przeładujesz, w przeciwnym razie to działa.
— Tony M

To zadziałało dla mnie po dodaniu czeku, czy strona otwiera się na dole.
— scott

6

Spędziłem całe wieki, próbując zrozumieć, dlaczego mój kod nie działa -

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

Problem był w moim CSS -

body { height: 100%};

autoZamiast tego ustawiłem go na (i martwiłem się, dlaczego jest ustawiony na100% na pierwszym miejscu). To naprawiło to dla mnie.


2

Możesz uniknąć tego problemu, używając wtyczki - a dokładniej mojej wtyczki :)

Poważnie, mimo że podstawowy problem już dawno został rozwiązany (różne przeglądarki używają różnych elementów do przewijania okien), istnieje kilka nietrywialnych problemów, które mogą Cię zaskoczyć:

Jestem oczywiście stronniczy, ale jQuery.scrollable jest w rzeczywistości dobrym wyborem do rozwiązania tych problemów. (W rzeczywistości nie znam żadnej innej wtyczki, która obsługuje je wszystkie).

Ponadto, można obliczyć pozycję docelową - ten, który wskażesz - w kuloodporną sposób z tej getScrollTargetPosition()funkcji w tym GIST .

Wszystko to zostawiłoby cię z

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

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

    return false;
}

1

Uważaj na to. Miałem ten sam problem, ani przeglądarka Firefox, ani Explorer z przewijaniem

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

Więc użyłem tak, jak powiedział David

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

Świetnie zadziałało, ale nowy problem, ponieważ są dwa elementy, body i html, funkcja jest wykonywana dwa razy, to znaczy X działa dwa razy.

próbowano tylko z „html”, a Firefox i Explorer działają, ale teraz Chrome tego nie obsługuje.

Tak potrzebne body dla Chrome i HTML dla Firefoksa i Explorera. Czy to błąd jQuery? nie wiem.

Uważaj tylko na swoją funkcję, ponieważ będzie działać dwukrotnie.


czy znalazłeś obejście tego problemu? A ponieważ wykrywanie przeglądarki jquery jest przestarzałe, nie wiem, jak mogę użyć wykrywania funkcji do rozróżnienia między Chrome i Firefox. Ich dokumentacja nie określa funkcji, która jest obecna w Chrome, a nie w Firefoksie lub odwrotnie. :(
— Mandeep Jain

2
a co z .stop (prawda, prawda) .animate?
— Toni Michel Caubet

0

Polecam nie opierając się bodyani htmljako bardziej przenośne rozwiązanie. Po prostu dodaj w treści element div, który ma zawierać przewijane elementy i nadaj mu styl, aby umożliwić przewijanie w pełnym rozmiarze:

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

(zakładając, że display:block;i top:0;left:0;są domyślne, który pasuje do celu), a następnie użyć $('#my-scroll')dla animacji.


0

To jest prawdziwa okazja. Działa bezbłędnie w Chrome i Firefox. To nawet smutne, że niektórzy ignoranci odrzucają mnie. Ten kod dosłownie działa doskonale, tak jak we wszystkich przeglądarkach. Wystarczy, że dodasz link i umieścisz id elementu, który chcesz przewinąć w href i działa on bez określania czegokolwiek. Czysty i niezawodny kod wielokrotnego użytku.

$(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 [];
  }
});

1
Może to być skomplikowane, ale jest naprawdę przydatne. Zasadniczo działa Plug-N-Play we wszystkich przeglądarkach. Prostsze rozwiązanie może na to nie pozwolić.
— drjorgepolanco

0

Niedawno napotkałem ten sam problem i rozwiązałem go, wykonując następujące czynności:

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

I youpi !!! działa na wszystkich przeglądarkach.

w przypadku, gdy pozycjonowanie nie jest poprawne, możesz w ten sposób odjąć wartość od offset () .top

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

To rozwiązanie jest już wspomniane w istniejących odpowiedziach, ale dziękuję za udostępnienie
— Toni Michel Caubet

Ta metoda nadal napotyka usterkę w przeglądarce Firefox, jak wspomniano w właśnie opublikowanym pytaniu: stackoverflow.com/q/58617731/1056713
— Chaya Cooper

-3

Dla mnie problem polegał na tym, że Firefox automatycznie przeskoczył do kotwicy z atrybutem nazwy takim samym jak nazwa skrótu, którą umieściłem w adresie URL. Mimo że wstawiłem .preventDefault (), aby temu zapobiec. Więc po zmianie atrybutów nazwy, firefox nie przeskoczył automatycznie do zakotwiczeń, ale poprawnie wykonał animację.

@Toni Przepraszamy, jeśli to nie było zrozumiałe. Rzecz w tym, że zmieniłem skróty w adresie URL, na przykład www.someurl.com/#hashname. Wtedy miałem na przykład kotwicę podobną <a name="hashname" ...></a>do, do której jQuery ma przewijać się automatycznie. Ale tak się nie stało, ponieważ przeskoczył bezpośrednio do kotwicy z pasującym atrybutem nazwy w przeglądarce Firefox bez żadnej animacji przewijania. Kiedy zmieniłem atrybut nazwy na coś innego niż nazwa skrótu, na przykład na name="hashname-anchor", przewijanie zadziałało.


-5

Dla mnie było to unikanie dodawania ID w momencie animacji:

Unikanie:

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

Przygotowując identyfikator wcześniej i robiąc to:

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

Naprawiono w FF. (Dodatki CSS nie zrobiły dla mnie różnicy)


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

Mam nadzieję, że to zadziała.


4
Jak tu pomaga setTimeut?
— Toni Michel Caubet

2
@ToniMichelCaubet przypuszczalnie celem jest uczynienie animacji asynchroniczną. Animacje jQuery są już jednak asynchroniczne, więc nic nie daje.
— mwcz

Nie jest to przemyślana odpowiedź na problem. Nie jestem pewien, co to da.
— wcześniejszy
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.