Animate scrollTop no funciona en firefox

164

Esta función funciona bien. Desplaza el cuerpo al desplazamiento del contenedor deseado

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

Pero no en Firefox. ¿Por qué?

-EDITAR-

Para manejar el desencadenante doble en la respuesta aceptada, sugiero detener el elemento antes de la animación:

$('body,html').stop(true,true).animate({scrollTop: stop}, delay);
Toni Michel Caubet
fuente
2
Su código final es el más elegante de todas las cosas aquí.
Lizardx

Respuestas:

331

Firefox coloca el desbordamiento en el htmlnivel, a menos que esté específicamente diseñado para comportarse de manera diferente.

Para que funcione en Firefox, use

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

Ejemplo de trabajo

La solución CSS sería establecer los siguientes estilos:

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

Supongo que la solución JS sería menos invasiva.


Actualizar

Gran parte de la discusión a continuación se centra en el hecho de que la animación scrollTopde dos elementos provocaría que la devolución de llamada se invoque dos veces. Las características de detección del navegador han sido sugeridas y posteriormente desaprobadas, y algunas son posiblemente exageradas.

Si la devolución de llamada es idempotente y no requiere mucha potencia informática, dispararla dos veces puede ser un completo problema. Si las invocaciones múltiples de la devolución de llamada son realmente un problema, y ​​si desea evitar la detección de características, podría ser más sencillo exigir que la devolución de llamada solo se ejecute una vez desde dentro de la devolución de llamada:

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
fuente
51
¡La solución JS tiene un defecto! La función animada se ejecuta dos veces, para el elemento html y para el elemento body respectivamente. Parece que los navegadores Webkit usan body y el resto usa html . Esta solución debería hacer el trabajo$(jQuery.browser.webkit ? "body": "html").animate(...);
Andreyco
2
Descubrí que la solución de Andreyco no funcionaba en jQuery <1.4 que estaba usando. Yo era capaz de solucionar de esta manera: $(jQuery.browser.mozilla ? "html" : "body").animate(...);. Sería genial no utilizar el rastreo de exploración, sino la detección de funciones. Sin embargo
Rustavore
23
Nota, jQuery.browserestá en desuso y falta en la última versión de jQuery
spiderplant0
2
4 años después, y esta respuesta sigue demostrando ser útil. ¡Muchas gracias!
Matt
1
@DamFa: Principalmente, porque window.scrollno anima. Por supuesto, podrías rodar lo tuyo con intervalos y scrollBy. Si desea agregar más facilidades a eso, de repente tiene que escribir mucho código para un aspecto bastante menor de su producto.
David Hedlund el
19

La detección de características y la animación en un solo objeto compatible sería bueno, pero no hay una solución de una línea. Mientras tanto, aquí hay una manera de usar una promesa de hacer una única devolución de llamada por ejecución.

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

ACTUALIZACIÓN: así es como podría usar la detección de funciones en su lugar. Este fragmento de código debe evaluarse antes de su llamada de animación:

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

Ahora use la var que acabamos de definir como el selector para la animación de desplazamiento de página, y use la sintaxis regular:

$(scrollTopElement).animate({ scrollTop: 100 }, 500, function() {
    // normal callback
});
Stephen
fuente
Funciona muy bien en circunstancias normales, ¡gracias! Sin embargo, hay un par de trampas a tener en cuenta. (1) Si el cuerpo no tiene suficiente contenido para desbordar la ventana cuando se ejecuta la prueba de características, la ventana no se desplazará y la prueba arrojará un resultado falso de "cuerpo". (2) Si la prueba se ejecuta dentro de un iframe en iOS, falla por la misma razón. Los iframes en iOS se expanden verticalmente (no horizontalmente) hasta que el contenido se ajusta dentro sin desplazarse. (3) Debido a que la prueba se ejecuta en la ventana principal, desencadena controladores de desplazamiento que ya están en su lugar. ... (continuación)
hashchange
... Por estas razones, una prueba a prueba de balas tendría que ejecutarse dentro de un iframe dedicado (resuelve (3)) con un contenido suficientemente grande (resuelve (1)) que se prueba para desplazamiento horizontal (resuelve (2)).
hashchange del
De hecho, tuve que ejecutar esta función en un script de tiempo de espera para hacer que esto dé resultados consistentes, de lo contrario, a veces parecía regresar htmly otras veces regresar body. Aquí está mi código después de declarar la función. var scrollTopElement; setTimeout( function() { scrollTopElement = getScrollTopElement(); console.log(scrollTopElement); }, 1000); Gracias por esto, ha resuelto mi problema.
Tony M
No importa, descubrí cuál es el problema real. Si ya está en la parte inferior de la página cuando vuelve a cargar (f5), este script regresará en htmllugar de bodycomo se supone. Esto es solo si te desplazas por la página web y vuelves a cargar, de lo contrario funciona.
Tony M
Esto funcionó para mí después de agregar una verificación de si la página se abre en la parte inferior.
scott
6

Pasé años tratando de averiguar por qué mi código no funcionaría.

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

El problema estaba en mi css -

body { height: 100%};

Lo configuré en su autolugar (y me quedé preocupándome por qué se configuró 100%en primer lugar). Eso me lo arregló.

Aidan Ewen
fuente
2

Es posible que desee evitar el problema utilizando un complemento, más específicamente, mi complemento :)

En serio, a pesar de que el problema básico se ha abordado desde hace mucho tiempo (diferentes navegadores usan diferentes elementos para el desplazamiento de la ventana), hay bastantes problemas no triviales en la línea que pueden hacer que te tropieces:

Obviamente soy parcial, pero jQuery.scrollable es en realidad una buena opción para abordar estos problemas. (De hecho, no conozco ningún otro complemento que los maneje a todos).

Además, puede calcular la posición de destino, a la que se desplaza, de forma a prueba de balas con la getScrollTargetPosition()función en esta esencia .

Todo lo cual te dejaría con

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

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

    return false;
}
hashchange
fuente
1

Cuidado con esto. Tuve el mismo problema, ni Firefox ni Explorer se desplazaban con

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

Así que usé como dijo David

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

Estuvo genial, pero nuevo problema, ya que hay dos elementos, cuerpo y html, la función se ejecuta dos veces, es decir, do X se ejecuta dos veces.

intentado solo con 'html', y Firefox y Explorer funcionan, pero ahora Chrome no lo admite.

Tan necesario cuerpo para Chrome, y html para Firefox y Explorer. ¿Es un error jQuery? no lo se

Solo tenga cuidado con su función, ya que se ejecutará dos veces.

Avenida Gez
fuente
¿Encontraste una solución para esto? Y dado que la detección del navegador jquery está en desuso, no sé cómo puedo usar la detección de funciones para distinguir entre Chrome y Firefox. Su documentación no especifica una característica que está presente en Chrome y no en Firefox o viceversa. :(
Mandeep Jain
2
¿Qué pasa con .stop (verdadero, verdadero) .animate?
Toni Michel Caubet
0

Recomendaría no confiar bodyni htmlcomo una solución más portátil. Simplemente agregue un div en el cuerpo que tenga como objetivo contener los elementos desplazados y darle un estilo que permita el desplazamiento a tamaño completo:

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

(suponiendo que display:block;y top:0;left:0;son valores predeterminados que se adapte a su objetivo), a continuación, utilizar $('#my-scroll')para sus animaciones.

Javarome
fuente
0

Este es el verdadero negocio. Funciona en Chrome y Firefox sin problemas. Incluso es triste que algunos ignorantes me rechacen. Este código literalmente funciona perfectamente como está en todos los navegadores. Solo necesita agregar un enlace y poner la identificación del elemento que desea desplazar en href y funciona sin especificar nada. Código puro reutilizable y confiable.

$(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
fuente
1
Puede ser complejo, pero es realmente útil. Básicamente funciona Plug-N-Play en todos los navegadores. Una solución más simple podría no permitirle hacer eso.
drjorgepolanco
0

Encontré el mismo problema recientemente y lo resolví haciendo esto:

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

Y youpi !!! Funciona en todos los navegadores.

en caso de que el posicionamiento no sea correcto, puede restar un valor del offset () .top haciendo esto

$ ('html, body'). animate ({scrollTop: $ ('. class_of_div'). offset () .top-desired_value}, 'fast'});
Balthazar DOSSOU
fuente
Esta solución ya se menciona en las respuestas existentes, pero gracias por compartirla
Toni Michel Caubet
Este método todavía encuentra una falla en Firefox, como se señaló en la pregunta que acabo de publicar: stackoverflow.com/q/58617731/1056713
Chaya Cooper
-3

Para mí, el problema era que Firefox saltó automáticamente al ancla con el atributo de nombre igual que el nombre hash que puse en la URL. Aunque puse .preventDefault () para evitar eso. Entonces, después de cambiar los atributos del nombre, Firefox no saltó automáticamente a los anclajes, sino que realizó la animación correctamente.

@Toni Lo siento si esto no fue comprensible. La cosa es que cambié los hashes en la URL como www.someurl.com/#hashname. Luego tuve, por ejemplo, un ancla como <a name="hashname" ...></a>a la que jQuery debería desplazarse automáticamente. Pero no lo hizo porque saltó directamente al ancla con el atributo de nombre correspondiente en Firefox sin ninguna animación de desplazamiento. Una vez que cambié el atributo de nombre a algo diferente del nombre hash, por ejemplo name="hashname-anchor", el desplazamiento funcionó.

Peter
fuente
-5

Para mí, fue evitar agregar la ID en el punto de animación:

Evitar

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

Preparando la identificación de antemano y haciendo esto en su lugar:

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

Solucionado en FF. (Las adiciones de CSS no hicieron una diferencia para mí)

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

Espero que esto funcione.

Simbu
fuente
44
¿Cómo ayuda el setTimeut, aquí?
Toni Michel Caubet
2
@ToniMichelCaubet presumiblemente el objetivo es hacer que la animación sea asíncrona. Sin embargo, las animaciones de jQuery ya son asíncronas, por lo que esto no logra nada.
mwcz
No es una respuesta reflexiva al problema. No estoy seguro de lo que esto logrará.
erier