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.