Animate scroll: Top non funziona in Firefox


164

Questa funzione funziona bene. Fa scorrere il corpo fino all'offset del contenitore desiderato

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

Ma non in Firefox. Perché?

-MODIFICARE-

Per gestire il doppio trigger nella risposta accettata, suggerisco di interrompere l'elemento prima dell'animazione:

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

2
Il tuo codice finale è il più elegante di tutte le cose qui.
Lizardx,

Risposte:


331

Firefox posiziona l'overflow a htmllivello, a meno che non sia stato progettato specificatamente per comportarsi diversamente.

Per farlo funzionare in Firefox, utilizzare

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

Esempio funzionante

La soluzione CSS sarebbe quella di impostare i seguenti stili:

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

Suppongo che la soluzione JS sarebbe meno invasiva.


Aggiornare

Gran parte della discussione che segue si concentra sul fatto che l'animazione scrollTopdei due elementi causerebbe il richiamo del callback due volte. Le funzionalità di rilevamento del browser sono state suggerite e successivamente deprecate, e alcune sono probabilmente piuttosto inverosimili.

Se il callback è idempotente e non richiede molta potenza di elaborazione, licenziarlo due volte potrebbe essere un non-problema completo. Se più invocazioni del callback sono veramente un problema e se si desidera evitare il rilevamento di funzionalità, potrebbe essere più semplice imporre che il callback venga eseguito una sola volta dall'interno del 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');
}));

51
La soluzione JS ha un difetto! la funzione animate viene eseguita due volte, rispettivamente per l' elemento html e per l' elemento body . Sembra che i browser Webkit utilizzino body e il resto usi HTML . Questa correzione dovrebbe funzionare$(jQuery.browser.webkit ? "body": "html").animate(...);
Andreyco il

2
Ho scoperto che la soluzione di Andreyco non funzionava in jQuery <1.4 che stavo usando. Sono stato in grado di risolvere il problema in questo modo: $(jQuery.browser.mozilla ? "html" : "body").animate(...);. Sarebbe bello non usare lo sniffing di ricerca, ma il rilevamento delle funzionalità. Non sono sicuro di come eseguire il rilevamento delle funzionalità nel CSS
Rustavore,

23
Nota, jQuery.browserè deprecato e mancante dall'ultima versione di jQuery
spiderplant0

2
4 anni dopo, e questa risposta si sta ancora rivelando utile. Grazie mille!
Matt,

1
@DamFa: principalmente perché window.scrollnon si anima. Ovviamente si potrebbe rotolare le tue cose con intervalli e scrollBy. Se vuoi aggiungere facilità a questo, improvvisamente hai un sacco di codice da scrivere per un aspetto piuttosto minore del tuo prodotto.
David Hedlund,

19

Il rilevamento delle caratteristiche e quindi l'animazione su un singolo oggetto supportato sarebbe utile, ma non esiste una soluzione a una riga. Nel frattempo, ecco un modo per utilizzare una promessa per eseguire un singolo callback per esecuzione.

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

AGGIORNAMENTO: Ecco come utilizzare invece il rilevamento delle funzionalità. Questo blocco di codice deve essere valutato prima della chiamata di animazione:

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

Ora usa il var che abbiamo appena definito come il selettore per l'animazione di scorrimento della pagina e usa la sintassi normale:

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

Funziona alla grande in circostanze normali, grazie! Ci sono un paio di gotcha di cui essere consapevoli, però. (1) Se il corpo non ha abbastanza contenuto per traboccare la finestra quando viene eseguito il test di funzionalità, la finestra non scorrerà e il test restituirà un risultato "body" fasullo. (2) Se il test viene eseguito all'interno di un iframe in iOS, non riesce per lo stesso motivo. Gli Iframe in iOS si espandono in verticale (non in orizzontale) fino a quando il contenuto non si adatta all'interno senza scorrere. (3) Poiché il test viene eseguito nella finestra principale, innesca gestori di scorrimento che sono già in atto. ... (continua)
hashchange

... Per questi motivi, un test antiproiettile dovrebbe essere eseguito all'interno di un iframe dedicato (risolve (3)) con contenuto sufficientemente ampio (risolve (1)) che viene testato per lo scorrimento orizzontale (risolve (2)).
Scambio hash

In realtà ho dovuto eseguire questa funzione in uno script di timeout per ottenere risultati coerenti, altrimenti a volte sembrava tornare htmle talvolta tornare body. Ecco il mio codice dopo aver dichiarato la funzione var scrollTopElement; setTimeout( function() { scrollTopElement = getScrollTopElement(); console.log(scrollTopElement); }, 1000); Grazie per questo, ha risolto il mio problema.
Tony M,

Non importa, ho capito qual è il vero problema. Se sei già in fondo alla pagina quando ricarichi (f5), questo script tornerà htmlinvece che bodycome dovrebbe. Questo è solo se si scorre fino in fondo alla pagina Web e si ricarica, altrimenti funziona.
Tony M,

Questo ha funzionato per me dopo che ho aggiunto un controllo per se la pagina si apre in fondo.
Scott

6

Ho passato anni cercando di capire perché il mio codice non avrebbe funzionato -

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

Il problema era nel mio CSS -

body { height: 100%};

L'ho impostato autoinvece (e sono rimasto preoccupato per il motivo per cui è stato impostato 100%in primo luogo). Ciò ha risolto il problema per me.


2

Potresti voler evitare il problema utilizzando un plug-in - più specificamente, il mio plug-in :)

Seriamente, anche se il problema di base è stato da tempo risolto (diversi browser utilizzano elementi diversi per lo scorrimento delle finestre), ci sono alcuni problemi non banali lungo la linea che possono farti inciampare:

Sono ovviamente di parte, ma jQuery.scrollable è in realtà una buona scelta per affrontare questi problemi. (In realtà, non conosco nessun altro plugin che li gestisca tutti.)

Inoltre, puoi calcolare la posizione target - quella a cui scorri - in modo a prova di proiettile con la getScrollTargetPosition()funzione in questa sintesi .

Tutto ciò ti lascerebbe con

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

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

    return false;
}

1

Attenzione a questo. Ho avuto lo stesso problema, né Firefox o Explorer scorrevano con

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

Quindi ho usato come diceva David

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

Grande ha funzionato, ma nuovo problema, dato che ci sono due elementi, body e html, la funzione viene eseguita due volte, ovvero X viene eseguito due volte.

provato solo con 'html', e Firefox ed Explorer funzionano, ma ora Chrome non lo supporta.

Quindi corpo necessario per Chrome e HTML per Firefox ed Explorer. È un bug di jQuery? non lo so.

Fai attenzione alla tua funzione, poiché verrà eseguita due volte.


hai trovato una soluzione per questo? E poiché il rilevamento del browser jquery è deprecato, non so come posso usare il rilevamento delle funzionalità per distinguere tra Chrome e Firefox. La loro documentazione non specifica una funzione presente in Chrome e non in Firefox o Viceversa. :(
Mandeep Jain,

2
che dire di .stop (vero, vero) .animato?
Toni Michel Caubet,

0

Consiglierei di non fare affidamento bodyhtmlcome soluzione più portatile. Aggiungi semplicemente un div nel corpo che abbia lo scopo di contenere gli elementi scorrevoli e lo stile come per consentire lo scorrimento a grandezza naturale:

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

(assumendo che display:block;e top:0;left:0;sono default che corrisponde al tuo obiettivo), quindi utilizzare $('#my-scroll')per le animazioni.


0

Questo è il vero affare. Funziona perfettamente su Chrome e Firefox. È anche triste che alcuni ignoranti mi votino. Questo codice funziona letteralmente perfettamente come su tutti i browser. Devi solo aggiungere un link e inserire l'id dell'elemento che vuoi scorrere nell'href e funziona senza specificare nulla. Codice puro riutilizzabile e affidabile.

$(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
Potrebbe essere complesso, ma è davvero utile. Funziona praticamente Plug-N-Play in tutti i browser. Una soluzione più semplice potrebbe non permetterti di farlo.
drjorgepolanco,

0

Ho riscontrato lo stesso problema solo di recente e l'ho risolto facendo questo:

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

E youpi !!! funziona su tutti i browser.

nel caso in cui il posizionamento non sia corretto, è possibile sottrarre un valore dall'offset () .top in questo modo

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

Questa soluzione è già menzionata nelle risposte esistenti, ma grazie per la condivisione
Toni Michel Caubet,

Questo metodo riscontra ancora un problema tecnico in Firefox, come notato nella domanda che ho appena pubblicato: stackoverflow.com/q/58617731/1056713
Chaya Cooper

-3

Per me il problema era che firefox passava automaticamente all'ancora con l'attributo name uguale al nome hash che ho inserito nell'URL. Anche se ho messo .preventDefault () per impedirlo. Quindi, dopo aver modificato gli attributi del nome, firefox non è passato automaticamente alle ancore, ma ha eseguito l'animazione nel modo giusto.

@Toni Mi dispiace se questo non è comprensibile. Il fatto è che ho cambiato gli hash nell'URL come www.someurl.com/#hashname. Poi ho avuto ad esempio un'ancora come <a name="hashname" ...></a>a cui jQuery dovrebbe scorrere automaticamente. Ma non è successo perché è passato direttamente all'ancoraggio con l'attributo corrispondente in Firefox senza alcuna animazione di scorrimento. Una volta modificato l'attributo name in qualcosa di diverso dal nome hash, ad esempio name="hashname-anchor", lo scorrimento ha funzionato.


-5

Per me, stava evitando di aggiungere l'ID nel punto di animazione:

Evitare:

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

Preparare in anticipo l'id e farlo invece:

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

Risolto in FF. (Le aggiunte CSS non hanno fatto la differenza per me)


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

Spero che funzioni.


4
Come aiuta setTimeut, qui?
Toni Michel Caubet,

2
@ToniMichelCaubet presumibilmente l'obiettivo è rendere l'animazione asincrona. Le animazioni di jQuery sono già asincrone, quindi questo non compie nulla.
mwcz,

Non una risposta ponderata al problema. Non sono sicuro di cosa riuscirà.
erier,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.