events scroll: requestAnimationFrame VS requestIdleCallback VS récepteurs d'événements passifs

comme nous le savons, il est souvent conseillé de débondir les écouteurs de scroll pour que UX soit meilleur lorsque l'utilisateur défile.

Cependant, j'ai souvent trouvé bibliothèques et articles où des gens influents comme Paul Lewis recommandent d'utiliser requestAnimationFrame. Toutefois, au fur et à mesure que la plate-forme web progresse, il est possible que certains conseils soient dépréciés au fil du temps.

le problème que je vois est qu'il y a des cas d'utilisation très différents pour gérer les événements de défilement, comme construire un site parallaxe, ou gérer un défilement infini et la pagination.

je vois 3 les principaux outils qui peuvent faire une différence en terme de UX:

donc, je voudrais savoir, par usecase( Je n'en ai que 2 mais vous pouvez en trouver d'autres), quel type d'outil devrais-je utiliser maintenant pour vous avez une très bonne expérience de défilement?

pour être plus précis, ma question principale serait plus liée aux vues et à la pagination infinies (qui n'ont généralement pas besoin de déclencher des animations visuelles, mais nous voulons une bonne expérience de défilement), est-il préférable de remplacer requestAnimationFrame avec un combo de requestIdleCallback + gestionnaire d'événements passif scroll ? Je me demande aussi quand il est logique d'utiliser requestIdleCallback pour appeler une API ou gérer la réponse de L'API pour laisser le scroll mieux fonctionner, ou est-ce qu'il est quelque chose que le navigateur peut déjà gérer pour nous?

20
demandé sur Drewness 2017-01-19 13:56:48

1 réponses

bien que cette question soit un peu plus ancienne, je veux y répondre parce que je vois souvent des scripts, où beaucoup de ces techniques sont mal utilisées.

en général tous vos outils demandés (rAF,rIC et les écouteurs passifs) sont d'excellents outils et ne disparaîtront pas de sitôt. Mais vous devez savoir pourquoi les utiliser.

avant de commencer: dans le cas où vous produisez des effets de scroll synced / scroll linked comme les effets de parallax / sticky elements, throttling en utilisant rIC,setTimeout ne pas parce que vous voulez réagir immédiatement.

requestAnimationFrame

rAF vous donne le point à l'intérieur du cycle de vie du cadre juste avant que le navigateur veut calculer le nouveau style et la mise en page du document. C'est pourquoi il est parfait pour les animations. Tout d'abord, il ne sera pas appelé plus souvent ou moins souvent que le navigateur calcule la disposition (bonne fréquence). Ensuite, il est appelé juste avant que le navigateur ne calcule la mise en page (bon timing). En fait, à l'aide de rAF pour toute modification de la mise en page (modifications DOM ou CSSOM) est très logique. rAF est synchronisé avec la V-SYNC comme tout autre rendu de mise en page dans le navigateur.

en utilisant rAF pour les gaz/anti-rebond

L'exemple par défaut de Paul Lewis ressemble à ceci:

var scheduledAnimationFrame;
function readAndUpdatePage(){
  console.log('read and update');
  scheduledAnimationFrame = false;
}

function onScroll (evt) {

  // Store the scroll value for laterz.
  lastScrollY = window.scrollY;

  // Prevent multiple rAF callbacks.
  if (scheduledAnimationFrame){
    return;
  }

  scheduledAnimationFrame = true;
  requestAnimationFrame(readAndUpdatePage);
}

window.addEventListener('scroll', onScroll);

Ce modèle est très souvent utilisé / copié, bien qu'il n'ait guère de sens dans la pratique. (Et je me demande pourquoi aucun développeur ne voit ce problème évident.) En général, en théorie, il est tout à fait logique d'étrangler tout au moins le rAF, parce qu'il n'est pas logique de demander des modifications de mise en page plus souvent que le navigateur ne rend la mise en page.

cependant le scroll événement est déclenché chaque fois que le navigateur rend un changement de position. Cela signifie un scroll événement est synchronisé avec le rendu de la page. Littéralement la même chose que rAF est de vous donner. Cela signifie qu'il n'est pas faites n'importe quel sens pour étrangler quelque chose par quelque chose, qui est déjà étranglé par la même chose exacte par définition.

dans la pratique, vous pouvez vérifier ce que je viens de dire en ajoutant un console.log et vérifiez à quelle fréquence ce pattern "empêche plusieurs callbacks rAF" (la réponse n'est pas, sinon ce serait un bug du navigateur).

  // Prevent multiple rAF callbacks.
  if (scheduledAnimationFrame){
    console.log('prevented rAF callback');
    return;
  }

comme vous verrez ce code n'est jamais exécuté, il est simplement du code mort.

Mais il y a une tendance similaire qui font sens pour un autre raison. Il ressemble à ceci:

//declare box, element, pos
function writeLayout(){
    element.classList.add('is-foo');
}

window.addEventListener('scroll', ()=> {
    box = element.getBoundingClientRect();

    if(box.top > pos){
        requestAnimationFrame(writeLayout);
    }
});

avec ce modèle, vous pouvez réduire ou même supprimer le frémissement de mise en page. L'idée est simple: à l'intérieur de votre écouteur de parchemin vous lisez layout et décidez si vous devez modifier le DOM et ensuite vous appelez la fonction qui modifie le DOM en utilisant rAF. Pourquoi est-ce utile? rAF s'assure que vous déplacez l'invalidation de votre mise en page (à l'ende du cadre). Cela signifie tout autre code qui est appelé dans le même cadre fonctionne sur un layout valide et peut fonctionner avec des méthodes de lecture de layout très rapides.

Ce modèle est en fait si grand, que je suggérerais la méthode d'aide suivante (écrite en ES5):

/**
 * @param fn {Function}
 * @param [throttle] {Boolean|undefined}
 * @return {Function}
 *
 * @example
 * //generate rAFed function
 * jQuery.fn.addClassRaf = bindRaf(jQuery.fn.addClass);
 *
 * //use rAFed function
 * $('div').addClassRaf('is-stuck');
 */
function bindRaf(fn, throttle){
    var isRunning, that, args;

    var run = function(){
        isRunning = false;
        fn.apply(that, args);
    };

    return function(){
        that = this;
        args = arguments;

        if(isRunning && throttle){return;}

        isRunning = true;
        requestAnimationFrame(run);
    };
}

requestIdleCallback

est de L'API similaire à rAF mais donne quelque chose de totalement différent. Il vous donne quelques périodes de repos à l'intérieur d'un cadre. (Normalement le point après que le navigateur a calculé la disposition et fait la peinture, mais il reste encore un certain temps jusqu'à ce que la v-sync arrive.) Même si la page est lente du point de vue de l'utilisateur, il peut y avoir des cadres, où le navigateur tourne au ralenti. Bien que rIC pouvez-vous donner le max. 50ms. La plupart du temps, vous avez entre 0,5 et 10 ms pour accomplir votre tâche. En raison du fait à quel point dans le cycle de vie du cadre rIC rappels sont appelés, vous ne devez pas modifier le DOM (utiliser rAF pour cela).

à la fin, il est tout à fait logique d'étrangler le scroll écouteur pour lazyloading, infinite le défilement et le de telles à l'aide de rIC. Pour ces types d'interfaces utilisateur vous pouvez même l'accélérateur plus et ajouter un setTimeout en face de lui. (donc vous faites 100m attendre et puis a rIC) (exemple de vie pour un anti-rebond et gaz.)

Ici également un article sur rAF, qui comprend deux schémas qui pourraient aider à comprendre les différents points à l'intérieur d'un "cadre" cycle de vie".

événement passif écouteur

les écouteurs D'événements passifs ont été inventés pour améliorer les performances de défilement. Les navigateurs modernes ont déplacé le défilement de la page (rendu de défilement) du fil principal au fil de composition. (voir https://hacks.mozilla.org/2016/02/smoother-scrolling-in-firefox-46-with-apz/)

mais il y a des événements qui produisent le scrolling, qui peuvent être empêchés par le script (ce qui se produit dans le thread principal et peut donc inverser la performance amélioration.)

ce qui signifie que dès que l'un de ces écouteurs d'événements est lié, le navigateur doit attendre que ces écouteurs soient exécutés avant que le navigateur puisse calculer le parchemin. Ces événements sont principalement touchstart,touchmove,touchend,wheel et en théorie dans une certaine mesure keypress et keydown. scroll événement lui-même est l'un de ces événements. scroll event n'a pas d'action par défaut, qui peut être empêchée par script.

Cela signifie si vous n'utilisez pas preventDefault dans votre touchstart,touchmove,touchend et/ou wheel, utilisez toujours des écouteurs d'événements passifs et vous devriez vous en sortir.

si vous utilisez preventDefault, vérifiez si vous pouvez le remplacer par le CSS touch-action propriété ou l'abaisser au moins dans votre DOM tree (par exemple pas de délégation d'événement pour ces événements). Dans le cas de wheel écouteurs, vous pourriez être en mesure de lier/délier sur mouseenter/mouseleave.

En cas de tout autre événement: C' ne pas utiliser la passivité des écouteurs d'événement pour améliorer les performances. Le plus important à noter: scroll événement ne peut pas être annulée et, par conséquent, il jamais CA a du sens d'utiliser des écouteurs d'événements passifs pour scroll.

Dans le cas d'un infini défilement vous n'avez pas besoin touchmove, vous avez seulement besoin d' scroll, donc les écouteurs d'événements passifs ne s'appliquent même pas.

reprendre

pour répondre À votre question

  • pour le lazyloading, vue infinie utilisez une combinaison de setTimeout+ requestIdleCallback pour vos écouteurs d'événement et utiliser rAF pour n'importe quelle mise en page écrit (mutations DOM).
  • pour l'instant les effets de toujours utiliser rAF pour n'importe quelle mise en page écrit (mutations DOM).
49
répondu alexander farkas 2017-12-24 11:51:45