Récemment, j'étudie les problèmes de performances du rendu des pages et de l'animation web, et je lis le chef-d'œuvre "CSS SECRET".
Cet article veut principalement parler de l'optimisation des pages optimisation du défilement.
Le contenu principal comprend pourquoi le défilement doit être optimisé événement , la relation entre le défilement et le rendu de la page, la limitation et l'anti-tremblement, les événements de pointeur : aucun n'optimise défilement. Étant donné que cet article couvre de nombreuses bases, vous pouvez vous référer aux points de connaissances ci-dessus et accéder de manière sélective à l'endroit correspondant pour lire.
L'optimisation du défilement ne fait en fait pas seulement référence à défilement (événements de défilement), y compris les événements déclenchés fréquemment tels que le redimensionnement. Jetez simplement un œil :
var i = 0; window.addEventListener('scroll',function(){ console.log(i++); },false);
Le résultat est le suivant :
Lors de la liaison d'événements tels que le défilement et le redimensionnement, lorsqu'ils se produisent, ils seront déclenchés très fréquemment et à des intervalles très rapprochés. Si l'événement implique beaucoup de calculs de position, de manipulations DOM, de redessinage d'éléments, etc. et que ces tâches ne peuvent pas être terminées avant le déclenchement du prochain événement de défilement, le navigateur supprimera des images. De plus, le défilement de la souris de l'utilisateur est souvent continu, ce qui continuera à déclencher l'événement de défilement, provoquant une augmentation des pertes d'images, une augmentation de l'utilisation du processeur du navigateur et une incidence sur l'expérience utilisateur.
Lier les rappels dans les événements de défilementIl existe de nombreux scénarios d'application, tels que le chargement paresseux et le glissement de imageschargement automatique< It est largement utilisé dans les 🎜>données, la barre flottante de navigation latérale, etc.
Le défilement fluide est un élément souvent négligé mais crucial de l'expérience utilisateur lors de la navigation sur le Web. Lorsque le défilement se comporte normalement, les utilisateurs auront l'impression que l'application est très fluide et agréable. Au contraire, un défilement lourd, peu naturel et bloqué apportera un grand inconfort aux utilisateurs.
Je pense qu'en matière de technologie, nous devons la retracer à ses racines. Ne lisez pas l'article de quelqu'un d'autre disant que le défilement des événements entraînera un décalage et parlera d'un tas de solutions. techniques d'optimisation. C'est comme trouver un trésor et le prendre comme guide, ce dont nous avons besoin n'est pas d'appropriation mais de critique, et nous devons aller plus souvent à la source.
Partez du problème et recherchez étape par étape jusqu'à la fin. Ce n'est qu'ainsi que la solution sera facile à retenir.
J'ai prêché beaucoup de bêtises. Si vous n'aimez pas ça, ignorez-le. Retour au sujet Pour trouver l'entrée de l'optimisation, il faut savoir où se trouve le sujet. Le problème réside. Pour l'optimisation de la page, alors nous devons connaître le principe de rendu de la page :
Je parlerai également en détail du principe de rendu du navigateur dans mon article précédent, mais plus du point de vue du rendu de l'animation, en parlant de : [Animation Web] Mouvement planétaire 3D CSS et principe de rendu du navigateur.
Après y avoir réfléchi, je voudrais le décrire brièvement. Je constate que chaque fois que je passe en revue ces points de connaissance, j'obtiens de nouvelles informations. Cette fois, je change une image en <🎜. >chrome
ome en est un exemple L'affichage d'une page Web peut être pensé comme passant par les étapes suivantes : JavaScript : De manière générale, nous utiliserons JavaScript pour réaliser certains changements visuels. Par exemple, réalisez une animation ou ajoutez des éléments DOM à la page, etc. Style : Calculer le style Ce processus consiste à faire correspondre le style CSS correspondant pour chaque élément DOM en fonction du sélecteur CSS. Une fois cette étape terminée, il est déterminé quelles règles de style CSS doivent être appliquées à chaque élément DOM. Mise en page : mise en page L'étape précédente a déterminé les règles de style de chaque élément DOM. chaque élément du DOM. La taille et la position de l'affichage à l'écran. La disposition des éléments dans les pages Web est relative, donc les modifications apportées à la disposition d'un élément entraîneront des modifications dans la disposition des autres éléments. Par exemple, les modifications de la largeur de l'élément affecteront la largeur de ses éléments enfants, et les modifications de la largeur de ses éléments enfants continueront également à affecter ses éléments petits-enfants. Par conséquent, pour les navigateurs, le processus de mise en page est fréquent. Peinture : Le dessin est essentiellement le processus de remplissage des pixels. Y compris le dessin du texte, de la couleur, de l'image, de la bordure et de l'ombre, etc., c'est-à-dire tous les effets visuels d'un élément DOM. Généralement, ce processus de peinture se fait sur plusieurs couches. Composite : Les calques de rendu sont fusionnés Comme le montre l'étape précédente, le dessin des éléments DOM dans la page est. réalisé sur plusieurs couches. Une fois le processus de dessin terminé sur chaque calque, le navigateur fusionnera tous les calques en un seul calque dans un ordre raisonnable, puis l'affichera à l'écran. Ce processus est particulièrement important pour les pages comportant des éléments qui se chevauchent, car une fois les calques fusionnés dans le mauvais ordre, les éléments s'afficheront anormalement. Ici encore, la notion de calque (GraphicsLayer) est impliquée. Le calque GraphicsLayer est téléchargé sur le GPU sous la forme d'une texture (texture), qui est souvent utilisée maintenant. . Vous pouvez voir que l'accélération matérielle GPU est étroitement liée au concept de couche. Cependant, cela a peu d’importance pour l’optimisation du défilement de cet article. Ceux qui souhaitent en savoir plus peuvent rechercher davantage par eux-mêmes. Parmi eux, les comportements de défilement et de redimensionnement de l'utilisateur (c'est-à-dire faire glisser la page et changer la taille de la fenêtre) entraîneront un nouveau rendu continu de la page. avec des éléments , lorsque le contenu d'un certain calque change, il suffit de mettre à jour la structure du calque, et de redessiner et rastériser uniquement la partie modifiée de la structure du calque de rendu . , sans redessiner complètement. Évidemment, si quelque chose comme un site Web de parallaxe (consultez-moi) bouge lorsque vous faites défiler, il est possible de redimensionner de grandes zones de contenu sur plusieurs calques, ce qui entraîne beaucoup de travail de dessin. gestion des événements. Pour de tels problèmes d'événements déclenchés à haute fréquence (tels que le défilement de page, le redimensionnement de l'écran, la surveillance des entrées de l'utilisateur, etc.), ce qui suit présente 防抖技术即是可以把多个顺序地调用合并成一次,也就是在一定时间内,规定事件被触发的次数。 通俗一点来说,看看下面这个简化的例子: 上面简单的防抖的例子可以拿到浏览器下试一下,大概功能就是如果 500ms 内没有连续触发两次 scroll 事件,那么才会触发我们真正想在 scroll 事件中触发的函数。 上面的示例可以更好的封装一下: 防抖函数确实不错,但是也存在问题,譬如图片的懒加载,我希望在下滑过程中图片不断的被加载出来,而不是只有当我停止下滑时候,图片才被加载出来。又或者下滑时候的数据的 ajax 请求加载也是同理。 这个时候,我们希望即使页面在不断被滚动,但是滚动 handler 也可以以一定的频率被触发(譬如 250ms 触发一次),这类场景,就要用到另一种技巧,称为节流函数(throttling)。 节流函数,只允许一个函数在 X 毫秒内执行一次。 与防抖相比,节流函数最主要的不同在于它保证在 X 毫秒内至少执行一次我们希望触发的事件 handler。 与防抖相比,节流函数多了一个 mustRun 属性,代表 mustRun 毫秒内,必然会触发一次 handler ,同样是利用定时器,看看简单的示例: 上面简单的节流函数的例子可以拿到浏览器下试一下,大概功能就是如果在一段时间内 scroll 触发的间隔一直短于 500ms ,那么能保证事件我们希望调用的 handler 至少在 1000ms 内会触发一次。 上面介绍的抖动与节流实现的方式都是借助了定时器 setTimeout ,但是如果页面只需要兼容高版本浏览器或应用在移动端,又或者页面需要追求高精度的效果,那么可以使用浏览器的原生方法 rAF(requestAnimationFrame)。 window.requestAnimationFrame() 这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数。这个方法接受一个函数为参,该函数会在重绘前调用。 rAF 常用于 web 动画的制作,用于准确控制页面的帧刷新渲染,让动画效果更加流畅,当然它的作用不仅仅局限于动画制作,我们可以利用它的特性将它视为一个定时器。(当然它不是定时器) 通常来说,rAF 被调用的频率是每秒 60 次,也就是 1000/60 ,触发频率大概是 16.7ms 。(当执行复杂操作时,当它发现无法维持 60fps 的频率时,它会把频率降低到 30fps 来保持帧数的稳定。) 简单而言,使用 requestAnimationFrame 来触发滚动事件,相当于上面的: 简单的示例如下: 上面简单的使用 rAF 的例子可以拿到浏览器下试一下,大概功能就是在滚动的过程中,保持以 16.7ms 的频率触发事件 handler。 使用 requestAnimationFrame 优缺点并存,首先我们不得不考虑它的兼容问题,其次因为它只能实现以 16.7ms 的频率来触发,代表它的可调节性十分差。但是相比 throttle(func, xx, 16.7) ,用于更复杂的场景时,rAF 可能效果更佳,性能更好。 总结一下 防抖动:防抖技术即是可以把多个顺序地调用合并成一次,也就是在一定时间内,规定事件被触发的次数。 节流函数:只允许一个函数在 X 毫秒内执行一次,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用。 rAF:16.7ms 触发一次 handler,降低了可控性,但是提升了性能和精确度。 上面介绍的方法都是如何去优化 scroll 事件的触发,避免 scroll 事件过度消耗资源的。 但是从本质上而言,我们应该尽量去精简 scroll 事件的 handler ,将一些变量的初始化、不依赖于滚动位置变化的计算等都应当在 scroll 事件外提前就绪。 建议如下: 输入事件处理函数,比如 scroll / touch 事件的处理,都会在 requestAnimationFrame 之前被调用执行。 因此,如果你在 scroll 事件的处理函数中做了修改样式属性的操作,那么这些操作会被浏览器暂存起来。然后在调用 requestAnimationFrame 的时候,如果你在一开始做了读取样式属性的操作,那么这将会导致触发浏览器的强制同步布局。 大部分人可能都不认识这个属性,嗯,那么它是干什么用的呢? pointer-events 是一个 CSS 属性,可以有多个不同的值,属性的一部分值仅仅与 SVG 有关联,这里我们只关注 pointer-events: 的情况,大概的意思就是禁止鼠标行为,应用了该属性后,譬如鼠标点击,hover 等功能都将失效,即是元素不会成为鼠标事件的 target。 可以就近 F12 打开开发者工具面板,给 标签添加上 pointer-events: 那么它有什么用呢? pointer-events: 可用来提高滚动时的帧频。的确,当滚动时,鼠标悬停在某些元素上,则触发其上的 hover 效果,然而这些影响通常不被用户注意,并多半导致滚动出现问题。对 body 元素应用 pointer-events: ,禁用了包括 hover 在内的鼠标事件,从而提高滚动性能。 大概的做法就是在页面滚动的时候, 给 添加上 .disable-hover 样式,那么在滚动停止之前, 所有鼠标事件都将被禁止。当滚动结束之后,再移除该属性。 可以查看这个 demo 页面。 上面说 pointer-events: 段话摘自 pointer-events-MDN ,还专门有文章讲解过这个技术: 使用pointer-events:none实现60fps滚动 。 这就完了吗?没有,张鑫旭有一篇专门的文章,用来探讨 pointer-events:
结论见仁见智,使用 pointer-events:
实例解析防抖动(Debouncing)和节流阀(Throttling) 无线性能优化:Composite Javascript高性能动画与页面渲染 Google Developers--渲染性能 Web高性能动画 Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois! Anti-rebond et limitation L'événement de défilement
lui-même sera déclencher le nouveau rendu de la page. En même temps, le gestionnaire de l'événement scroll sera déclenché fréquemment. Par conséquent, il ne devrait pas y avoir d'opérations complexes à l'intérieur du gestionnaire d'événements, telles que des opérations DOM. ne doit pas être placé dans la 防抖(Debouncing)
// 简单的防抖动函数
function debounce(func, wait, immediate) {
// 定时器变量
var timeout;
return function() {
// 每次触发 scroll handler 时先清除定时器
clearTimeout(timeout);
// 指定 xx ms 后触发真正想进行的操作 handler
timeout = setTimeout(func, wait);
};
};
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
// 采用了防抖动
window.addEventListener('scroll',debounce(realFunc,500));
// 没采用防抖动
window.addEventListener('scroll',realFunc);
// 防抖动函数
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var myEfficientFn = debounce(function() {
// 滚动中的真正的操作
}, 250);
// 绑定监听
window.addEventListener('resize', myEfficientFn);
节流(Throttling)
// 简单的节流函数
function throttle(func, wait, mustRun) {
var timeout,
startTime = new Date();
return function() {
var context = this,
args = arguments,
curTime = new Date();
clearTimeout(timeout);
// 如果达到了规定的触发时间间隔,触发 handler
if(curTime - startTime >= mustRun){
func.apply(context,args);
startTime = curTime;
// 没达到触发间隔,重新设定定时器
}else{
timeout = setTimeout(func, wait);
}
};
};
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
// 采用了节流函数
window.addEventListener('scroll',throttle(realFunc,500,1000));
使用 rAF(requestAnimationFrame)触发滚动事件
requestAnimationFrame
throttle(func, xx, 1000/60) //xx 代表 xx ms内不会重复触发事件 handler
var ticking = false; // rAF 触发锁
function onScroll(){
if(!ticking) {
requestAnimationFrame(realFunc);
ticking = true;
}
}
function realFunc(){
// do something...
console.log("Success");
ticking = false;
}
// 滚动事件监听
window.addEventListener('scroll', onScroll, false);
简化 scroll 内的操作
避免在scroll 事件中修改样式属性 / 将样式操作从 scroll 事件中剥离
滑动过程中尝试使用 pointer-events: 禁止鼠标事件
.disable-hover {
pointer-events: none;
}