Animate scrollTop ne fonctionne pas dans Firefox

164

Cette fonction fonctionne très bien. Il fait défiler le corps jusqu'au décalage du conteneur souhaité

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

Mais pas dans Firefox. Pourquoi?

-ÉDITER-

Pour gérer le double déclenchement dans la réponse acceptée, je suggère d'arrêter l'élément avant l'animation:

$('body,html').stop(true,true).animate({scrollTop: stop}, delay);
Toni Michel Caubet
la source
2
Votre code final est le plus élégant de tous les éléments ici.
Lizardx

Réponses:

331

Firefox place le débordement au htmlniveau, sauf s'il est spécifiquement conçu pour se comporter différemment.

Pour le faire fonctionner dans Firefox, utilisez

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

Exemple de travail

La solution CSS serait de définir les styles suivants:

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

Je suppose que la solution JS serait la moins invasive.


Mettre à jour

Une grande partie de la discussion ci-dessous se concentre sur le fait que l'animation scrollTopde deux éléments entraînerait l'appel du rappel à deux reprises. Des fonctionnalités de détection de navigateur ont été suggérées puis obsolètes, et certaines sont sans doute plutôt exagérées.

Si le rappel est idempotent et ne nécessite pas beaucoup de puissance de calcul, le déclencher deux fois peut être un non-problème complet. Si plusieurs appels du rappel sont vraiment un problème et si vous souhaitez éviter la détection de fonctionnalité, il peut être plus simple de faire en sorte que le rappel ne soit exécuté qu'une seule fois à partir du rappel:

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
la source
51
La solution JS a un défaut! La fonction animate est exécutée deux fois, respectivement pour l' élément html et pour l' élément body . Il semble que les navigateurs Webkit utilisent body et le reste utilise du HTML . Ce correctif devrait faire le travail$(jQuery.browser.webkit ? "body": "html").animate(...);
Andreyco
2
J'ai trouvé que la solution d'Andreyco ne fonctionnait pas dans jQuery <1.4 que j'utilisais. J'ai pu le réparer comme ceci: $(jQuery.browser.mozilla ? "html" : "body").animate(...);. Ce serait bien de ne pas utiliser le sniffing de navigation, mais la détection de fonctionnalités. Je ne sais pas comment faire la détection de fonctionnalités sur le CSS
Rustavore
23
Remarque: jQuery.browserest obsolète et manquant dans la dernière version de jQuery
spiderplant0
2
4 ans plus tard, et cette réponse s'avère toujours utile. Merci beaucoup!
Matt
1
@DamFa: Principalement, parce window.scrollqu'il n'anime pas. Vous pouvez bien sûr rouler votre propre truc avec des intervalles et scrollBy. Si vous souhaitez ajouter de la facilité à cela, vous avez soudainement beaucoup de code à écrire pour un aspect plutôt mineur de votre produit.
David Hedlund
19

La détection des fonctionnalités, puis l'animation sur un seul objet pris en charge serait bien, mais il n'y a pas de solution en une seule ligne. En attendant, voici un moyen d'utiliser une promesse pour faire un seul rappel par exécution.

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

MISE À JOUR: Voici comment vous pouvez utiliser la détection des fonctionnalités à la place. Ce morceau de code doit être évalué avant votre appel d'animation:

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

Maintenant, utilisez le var que nous venons de définir comme sélecteur pour l'animation de défilement de page, et utilisez la syntaxe normale:

$(scrollTopElement).animate({ scrollTop: 100 }, 500, function() {
    // normal callback
});
Stephen
la source
Fonctionne très bien dans des circonstances normales, merci! Il y a cependant quelques pièges à prendre en compte. (1) Si le corps n'a pas assez de contenu pour déborder de la fenêtre lorsque le test de fonctionnalité s'exécute, la fenêtre ne défilera pas et le test renvoie un faux "corps" résultat. (2) Si le test s'exécute dans une iframe sous iOS, il échoue pour la même raison. Les iframes dans iOS se développent verticalement (pas horizontalement) jusqu'à ce que le contenu rentre à l'intérieur sans défilement. (3) Puisque le test est exécuté dans la fenêtre principale, il déclenche des gestionnaires de défilement qui sont déjà en place. ... (a continué)
hashchange
... Pour ces raisons, un test pare-balles devrait s'exécuter dans une iframe dédiée (résout (3)) avec un contenu suffisamment grand (résout (1)) qui est testé pour le défilement horizontal (résout (2)).
hashchange le
En fait, j'ai dû exécuter cette fonction dans un script d'expiration pour que cela donne des résultats cohérents, sinon il semblait parfois revenir htmlet parfois revenir body. Voici mon code après avoir déclaré la fonction var scrollTopElement; setTimeout( function() { scrollTopElement = getScrollTopElement(); console.log(scrollTopElement); }, 1000); Merci pour cela, cela a résolu mon problème.
Tony M
Qu'à cela ne tienne, j'ai compris quel était le problème réel. Si vous êtes déjà au bas de la page lorsque vous rechargez (f5), ce script reviendra htmlau lieu de bodyce qu'il est supposé. Ce n'est que si vous faites défiler tout le long de la page Web et que vous rechargez, sinon cela fonctionne.
Tony M
Cela a fonctionné pour moi après avoir ajouté un chèque pour savoir si la page s'ouvre en bas.
scott le
6

J'ai passé des années à essayer de comprendre pourquoi mon code ne fonctionnait pas -

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

Le problème était dans mon css -

body { height: 100%};

Je l'ai réglé à la autoplace (et je me suis demandé pourquoi il avait été réglé 100%en premier lieu). Cela a réglé le problème pour moi.

Aidan Ewen
la source
2

Vous voudrez peut-être éviter le problème en utilisant un plugin - plus précisément, mon plugin :)

Sérieusement, même si le problème de base a été résolu depuis longtemps (différents navigateurs utilisent différents éléments pour le défilement de la fenêtre), il y a pas mal de problèmes non triviaux sur la ligne qui peuvent vous trébucher:

Je suis évidemment partial, mais jQuery.scrollable est en fait un bon choix pour résoudre ces problèmes. (En fait, je ne connais aucun autre plugin qui les gère tous.)

De plus, vous pouvez calculer la position cible - celle vers laquelle vous faites défiler - de manière à toute épreuve avec la getScrollTargetPosition()fonction dans cet essentiel .

Tout cela vous laisserait avec

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

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

    return false;
}
changement de hachage
la source
1

Méfiez-vous de cela. J'ai eu le même problème, ni Firefox ni Explorer ne faisant défiler avec

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

Alors j'ai utilisé comme David l'a dit

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

Super cela a fonctionné, mais nouveau problème, car il y a deux éléments, le corps et le html, la fonction est exécutée deux fois, c'est-à-dire, X exécute deux fois.

essayé uniquement avec «html», et Firefox et Explorer fonctionnent, mais maintenant Chrome ne le prend pas en charge.

Il faut donc du corps pour Chrome et du html pour Firefox et Explorer. Est-ce un bogue jQuery? ne sais pas.

Méfiez-vous simplement de votre fonction, car elle fonctionnera deux fois.

Avenida Gez
la source
avez-vous trouvé une solution de contournement pour cela? Et comme la détection du navigateur jquery est obsolète, je ne sais pas comment puis-je utiliser la détection de fonctionnalités pour faire la distinction entre Chrome et Firefox. Leur documentation ne spécifie pas une fonctionnalité présente dans Chrome et non dans Firefox ou vice versa. :(
Mandeep Jain
2
qu'en est-il de .stop (vrai, vrai) .animé?
Toni Michel Caubet
0

Je recommanderais de ne pas compter sur bodyni htmlcomme solution plus portable. Ajoutez simplement un div dans le corps qui vise à contenir les éléments défilés et le stylisez comme pour activer le défilement en taille réelle:

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

(en supposant que display:block;et que top:0;left:0;les valeurs par défaut correspondent à votre objectif), utilisez-les $('#my-scroll')pour vos animations.

Javarome
la source
0

C'est la vraie affaire. Cela fonctionne parfaitement sur Chrome et Firefox. Il est même triste que certains ignorants me votent contre. Ce code fonctionne littéralement parfaitement tel quel sur tous les navigateurs. Il vous suffit d'ajouter un lien et de mettre l'id de l'élément que vous souhaitez faire défiler dans le href et cela fonctionne sans rien spécifier. Code purement réutilisable et fiable.

$(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
la source
1
Cela peut être complexe, mais c'est vraiment utile. Il fonctionne essentiellement Plug-N-Play dans tous les navigateurs. Une solution plus simple pourrait ne pas vous permettre de faire cela.
drjorgepolanco
0

J'ai rencontré le même problème récemment et je l'ai résolu en faisant ceci:

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

Et youpi !!! cela fonctionne sur tous les navigateurs.

au cas où le positionnement n'est pas correct, vous pouvez soustraire une valeur de offset () .top en faisant ceci

$ ('html, body'). animate ({scrollTop: $ ('. class_of_div'). offset () .top-desired_value}, 'fast'});
Balthazar DOSSOU
la source
Cette solution est déjà mentionnée dans les réponses existantes mais merci pour le partage
Toni Michel Caubet
Cette méthode rencontre toujours un problème dans Firefox, comme indiqué dans la question que je viens de poster: stackoverflow.com/q/58617731/1056713
Chaya Cooper
-3

Pour moi, le problème était que Firefox sautait automatiquement à l'ancre avec le nom-attribut identique au nom de hachage que j'ai mis dans l'URL. Même si j'ai mis .preventDefault () pour éviter cela. Ainsi, après avoir modifié les attributs de nom, Firefox n'a pas automatiquement accédé aux ancres, mais a exécuté correctement l'animation.

@Toni Désolé si ce n'était pas compréhensible. La chose est que j'ai changé les hachages dans l'URL comme www.someurl.com/#hashname. Ensuite, j'ai eu par exemple une ancre comme <a name="hashname" ...></a>vers laquelle jQuery devrait défiler automatiquement. Mais ce n'est pas le cas, car il est passé directement à l'ancre avec l'attribut de nom correspondant dans Firefox sans aucune animation de défilement. Une fois que j'ai changé l'attribut de nom en quelque chose de différent du nom de hachage, par exemple name="hashname-anchor", le défilement a fonctionné.

Peter
la source
-5

Pour moi, cela évitait d'ajouter l'ID au point d'animation:

Éviter:

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

Préparer l'id au préalable et faire ceci à la place:

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

Corrigé dans FF. (Les ajouts css n'ont pas fait de différence pour moi)

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

J'espère que cela fonctionne.

Simbu
la source
4
Comment setTimeut aide-t-il, ici?
Toni Michel Caubet
2
@ToniMichelCaubet vise probablement à rendre l'animation asynchrone. Les animations jQuery sont déjà asynchrones, cependant, cela n'apporte rien.
mwcz
Pas une réponse réfléchie au problème. Je ne sais pas ce que cela va accomplir.
erier