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?
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 utiliserrAF
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).