최근에는 페이지 렌더링과 웹애니메이션의 성능 문제를 연구하며 명작 "CSS SECRET"을 읽고 있습니다.
이 글에서는 주로 페이지 최적화 스크롤 최적화에 대해 이야기하고 싶습니다.
주요 콘텐츠에는 스크롤을 최적화해야 하는 이유 이벤트 , 스크롤과 페이지 렌더링 간의 관계, 조절 및 흔들림 방지, 포인터 이벤트:없음 최적화가 포함됩니다. 스크롤. 이 글은 기본적인 내용을 많이 다루기 때문에 위의 지식 포인트를 참고하시고 해당 부분으로 선택적으로 이동하여 읽으시면 됩니다.
스크롤 최적화는 실제로 단순히 크기 조정과 같이 자주 발생하는 이벤트를 포함한 스크롤(스크롤 이벤트) 간단히 살펴보세요:
var i = 0; window.addEventListener('scroll',function(){ console.log(i++); },false);
출력은 다음과 같습니다:
스크롤이나 크기 조정과 같은 이벤트를 바인딩할 때 이벤트가 발생하면 매우 자주, 매우 가까운 간격으로 트리거됩니다. 이벤트에 많은 위치 계산, DOM 조작, 요소 다시 그리기 등이 포함되고 다음 스크롤 이벤트가 트리거되기 전에 이러한 작업을 완료할 수 없는 경우 브라우저에서 프레임이 삭제됩니다. 또한 사용자의 마우스 스크롤은 종종 연속적이어서 스크롤 이벤트를 계속 트리거하여 프레임 드롭이 확장되고 브라우저의 CPU 사용량이 증가하며 사용자 경험에 영향을 미칩니다.
스크롤 이벤트에서 콜백 바인딩애플리케이션또한 그림자동 로딩데이터, 사이드플로팅내비게이션 바 등에 널리 사용됩니다.
부드러운 스크롤은 종종 간과되지만 웹 탐색 시 사용자 경험에서 중요한 부분입니다. 스크롤이 정상적으로 작동할 때 사용자는 애플리케이션이 매우 부드럽고 쾌적하다고 느낄 것입니다. 반대로 무겁고 부자연스럽고 멈춘 스크롤은 사용자에게 큰 불편함을 줄 것입니다.
이유 스크롤링 이벤트를 최적화해야 합니까? 성능에 영향을 미치기 때문입니다. 그럼 어떤 성능에 영향을 미치나요? 음...이것은 페이지 성능 문제를 결정하는 것부터 시작됩니다.
문제부터 시작하여 끝까지 검색해 보면 문제의 핵심을 쉽게 찾을 수 있습니다. 이렇게 얻은 해결책만이 기억하기 쉽습니다.
나는 말도 안되는 소리를 많이 했습니다. 마음에 들지 않으면 그냥 무시하세요. 주제로 돌아가서 최적화의 입구를 찾으려면, 문제는 페이지 최적화에 있습니다. 페이지의 렌더링 원리를 알아야 합니다.
이전 기사에서 브라우저 렌더링 원리에 대해서도 자세히 설명하겠습니다. 애니메이션 렌더링의 관점에서: [웹 애니메이션] CSS3 3D 행성 이동 및 브라우저 렌더링 원리.
생각해보니 이런 지식 포인트들을 복습할 때마다 새로운 통찰을 얻게 되는 것 같아요. 이번에는 chrome은 다음 단계를 거쳐 웹 페이지를 표시하는 것으로 생각할 수 있습니다.
JavaScript: 일반적으로 JavaScript를 사용하여 몇 가지 시각적 변화를 달성합니다. 예를 들어 애니메이션을 만들거나 페이지에 일부 DOM 요소를 추가하는 등의 작업을 수행합니다.
스타일: 스타일을 계산하는 과정은 CSS 선택자를 기반으로 각 DOM 요소에 해당하는 CSS 스타일을 일치시키는 것입니다. 이 단계가 완료되면 각 DOM 요소에 어떤 CSS 스타일 규칙을 적용해야 하는지 결정됩니다.
레이아웃: 레이아웃은 각 DOM 요소의 스타일 규칙을 구체적으로 계산하는 단계입니다. 각 DOM 요소는 화면에 표시되는 크기와 위치입니다. 웹 페이지의 요소 레이아웃은 상대적이므로 한 요소의 레이아웃을 변경하면 다른 요소의 레이아웃도 변경됩니다. 예를 들어, 6c04bd5ca3fcae76e30b72ad730ca86d 요소의 너비를 변경하면 하위 요소의 너비에 영향을 미치며, 하위 요소의 너비를 변경하면 손자 요소에도 계속 영향을 줍니다. 따라서 브라우저의 경우 레이아웃 프로세스가 자주 발생합니다.
페인트: 그리기는 본질적으로 픽셀을 채우는 과정입니다. 그리기 텍스트, 색상, 이미지, 테두리 및 그림자 등, 즉 DOM 요소의 모든 시각적 효과를 포함합니다. 일반적으로 이 그리기 프로세스는 여러 레이어에서 수행됩니다.
복합: 렌더링 레이어가 병합되었습니다. 이전 단계에서 볼 수 있듯이 페이지의 DOM 요소 그리기는 다음과 같습니다. 여러 레이어에서 수행됩니다. 각 레이어에서 그리기 프로세스가 완료되면 브라우저는 모든 레이어를 합리적인 순서로 단일 레이어로 병합한 다음 화면에 표시합니다. 이 프로세스는 요소가 겹치는 페이지에 특히 중요합니다. 레이어가 잘못된 순서로 병합되면 요소가 비정상적으로 표시되기 때문입니다.
여기서 다시 레이어(GraphicsLayer)의 개념이 포함됩니다. GraphicsLayer 레이어는 현재 자주 사용되는 텍스처(texture)로 GPU에 업로드됩니다. GPU 하드웨어 가속은 소위 레이어 개념과 밀접한 관련이 있음을 알 수 있습니다. 그러나 이는 이 기사의 스크롤 최적화와 거의 관련이 없습니다. 더 많은 것을 배우고 싶은 사람들은 스스로 더 많은 것을 구글링할 수 있습니다.
간단히 말하면 웹 페이지가 생성되면 한 번 이상 렌더링(Layout+Paint)됩니다. 사용자가 방문하는 동안 계속 리플로우되고 다시 칠해집니다.그 중 사용자 스크롤 및 크기 조정
동작(즉, 페이지를 밀고 창 크기를 변경하는 것)으로 인해 페이지가 지속적으로 다시 렌더링됩니다.
페이지를 스크롤할 때 브라우저는 이러한 레이어(합성 레이어라고도 함)에 일부 픽셀을 그려야 할 수도 있습니다.요소를 요소로 그룹화하면 특정 레이어의 내용이 변경될 때 레이어 구조만 업데이트하고 렌더링 레이어 구조의 변경된 부분만 다시 그리고 래스터화하면 됩니다. . , 완전히 다시 그리지 않고. 분명히 시차 웹 사이트(체크 미 아웃)와 같은 것이 스크롤할 때 움직이면 여러 레이어에 걸쳐 넓은 콘텐츠 영역의 크기가 조정될 수 있으며 이로 인해 많은 그리기 작업이 발생하게 됩니다.
dler이 자주 발생하므로 이벤트 핸들러 내부에는 DOM 작업과 같은 복잡한 작업이 있어서는 안 됩니다. 이벤트 처리에 배치하면 안 됩니다.
페이지 스크롤, 화면 크기 조정, 사용자 입력 모니터링 등 자주 발생하는 이벤트 문제에 대해 다음에서는 일반적으로 사용되는 두 가지 솔루션을 소개합니다. : 흔들림 방지 및 스로틀링. 防抖技术即是可以把多个顺序地调用合并成一次,也就是在一定时间内,规定事件被触发的次数。 通俗一点来说,看看下面这个简化的例子: 上面简单的防抖的例子可以拿到浏览器下试一下,大概功能就是如果 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 打开开发者工具面板,给 6c04bd5ca3fcae76e30b72ad730ca86d 标签添加上 pointer-events: 那么它有什么用呢? pointer-events: 可用来提高滚动时的帧频。的确,当滚动时,鼠标悬停在某些元素上,则触发其上的 hover 效果,然而这些影响通常不被用户注意,并多半导致滚动出现问题。对 body 元素应用 pointer-events: ,禁用了包括 hover 在内的鼠标事件,从而提高滚动性能。 大概的做法就是在页面滚动的时候, 给 6c04bd5ca3fcae76e30b72ad730ca86d 添加上 .disable-hover 样式,那么在滚动停止之前, 所有鼠标事件都将被禁止。当滚动结束之后,再移除该属性。 可以查看这个 demo 页面。 上面说 pointer-events: 段话摘自 pointer-events-MDN ,还专门有文章讲解过这个技术: 使用pointer-events:none实现60fps滚动 。 这就完了吗?没有,张鑫旭有一篇专门的文章,用来探讨 pointer-events:
结论见仁见智,使用 pointer-events:
实例解析防抖动(Debouncing)和节流阀(Throttling) 无线性能优化:Composite Javascript高性能动画与页面渲染 Google Developers--渲染性能 Web高性能动画 防抖(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;
}
위 내용은 고성능 스크롤 및 페이지 렌더링 최적화의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!