web-dev-qa-db-fra.com

Animate scrollTop ne fonctionne pas dans Firefox

Cette fonction fonctionne bien. Il fait défiler le corps jusqu'à l'offset 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?

-MODIFIER-

Pour gérer le double déclencheur 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);
163
Toni Michel Caubet

Firefox place le débordement au niveau html, sauf s’il est spécifiquement stylé pour se comporter différemment.

Pour que cela fonctionne dans Firefox, utilisez

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

Exemple de travail

La solution CSS consisterait à 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 porte sur le fait que l’animation de la scrollTop de deux éléments entraînerait l’appel de rappel deux fois. Des fonctionnalités de détection de navigateur ont été suggérées et par la suite déconseillées, et certaines sont sans doute très recherchées.

Si le rappel est idempotent et ne nécessite pas beaucoup de puissance de calcul, le déclencher deux fois peut ne pas poser problème. Si plusieurs appels du rappel constituent réellement 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 de ce dernier:

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

Détecter les fonctionnalités puis animer sur un seul objet pris en charge serait agréable, mais il n’existe pas de solution sur une ligne. En attendant, voici un moyen d'utiliser une promesse d'effectuer un seul rappel par exécution.

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

UPDATE: Voici comment vous pouvez utiliser la détection de 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 la variable que nous venons de définir comme sélecteur pour l'animation de défilement de page, et utilisez la syntaxe standard:

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

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

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

Le problème était dans mon css -

body { height: 100%};

Je l'ai réglé sur auto à la place (et je me suis inquiété de la raison pour laquelle il avait été réglé sur 100% en premier lieu). Cela a réglé le problème pour moi.

6
Aidan Ewen

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 longtemps été abordé (différents navigateurs utilisent différents éléments pour le défilement de la fenêtre), il existe plusieurs problèmes non triviaux sur la ligne qui peuvent vous tromper:

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 sur laquelle vous faites défiler - de manière sécurisée avec la fonction getScrollTargetPosition() de ce Gist .

Tout ce qui vous laisserait avec

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

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

    return false;
}
2
hashchange

Attention à cela. J'ai eu le même problème, ni Firefox ni Explorer avec le défilement

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

J'ai donc utilisé comme dit David

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

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

essayé seulement avec 'html', et Firefox et Explorer fonctionnent, mais maintenant Chrome ne le supporte pas.

Corps si nécessaire pour Chrome et html pour Firefox et Explorer. Est-ce un bogue jQuery? ne sais pas.

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

1
Avenida Gez

Je recommanderais pas s'appuyer sur body ni html comme solution plus portable. Ajoutez simplement une div dans le corps qui vise à contenir les éléments défilés et stylez-la 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 top:0;left:0; sont des valeurs par défaut qui correspondent à votre objectif), utilisez ensuite $('#my-scroll') pour vos animations.

0
Javarome

J'ai rencontré le même problème tout récemment et je l'ai résolu en procédant comme suit:

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

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

si le positionnement est incorrect, vous pouvez soustraire une valeur de offset () .top en procédant ainsi.

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

C'est la vraie affaire. Cela fonctionne sur Chrome et Firefox sans faille. Il est même triste que des ignorants votent contre moi. Ce code fonctionne littéralement parfaitement tel quel sur tous les navigateurs. Il vous suffit d'ajouter un lien et de mettre l'identifiant de l'élément que vous voulez faire défiler dans le href et cela fonctionne sans rien spécifier. Code pur 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 [];
  }
});
0
drjorgepolanco