Home  >  Article  >  Web Front-end  >  High-performance scroll and page rendering optimization

High-performance scroll and page rendering optimization

PHPz
PHPzOriginal
2017-03-12 16:41:131339browse

Recently I have been studying the performance issues of page rendering and webanimation, and reading the masterpiece "CSS SECRET".

This article mainly wants to talk about page optimizationScroll optimization.

The main content includes why scrolling needs to be optimized Events, the relationship between scrolling and page rendering, throttling and anti-shake, pointer-events:none optimizes scrolling. Because this article covers many basics, you can refer to the above knowledge points and selectively jump to the corresponding place to read.

The origin of scrolling optimization

Scrolling optimization actually does not only refer to scrolling (scroll events), including events that are triggered frequently such as resize. Take a simple look:


var i = 0;
window.addEventListener('scroll',function(){
	console.log(i++);
},false);

The output is as follows:

When binding events such as scroll and resize, when they occur, they are triggered very frequently and at very close intervals. If the event involves a lot of position calculation, DOM manipulation, element redrawing, etc. and these tasks cannot be completed before the next scroll event is triggered, it will cause the browser to drop frames. In addition, the user's mouse scrolling is often continuous, which will continue to trigger the scroll event, causing frame drops to expand, the browser's CPU usage to increase, and the user experience to be affected.

Bind callbacks in scroll eventsApplicationThere are also many scenarios, including lazy loading and sliding of picturesautomatic loading Data, side Floating Navigation columns, etc. are widely used.

When users browse the web, having smooth scrolling is an often overlooked but crucial part of the user experience. When scrolling behaves normally, users will feel that the application is very smooth and pleasant. On the contrary, heavy, unnatural and stuck scrolling will bring great discomfort to users.

The relationship between scrolling and page rendering

Why scrolling Does the event need to be optimized? Because it affects performance. So what performance does it affect? Well...this starts with what determines page performance issues.

I think that when it comes to technology, we must go back to its roots. Don’t read someone else’s article saying that scrolling events will cause lag and talk about a bunch of solution optimization techniques. It’s as if you have found a treasure and regard it as a guide. , what we need is not appropriationism but criticism, and we need to go to the source more often.

Starting from the problem and searching step by step to the end, it is easy to find the crux of the problem. Only in this way can the solution be easy to remember.

I have preached a lot of nonsense. If you don’t like it, just ignore it. Back to the topic. To find the entrance to optimization, you need to know where the problem lies. For page optimization, then we You need to know the rendering principle of the page:

I will also talk about the browser rendering principle in detail in my previous article, but more from the perspective of animation rendering. Talking about: [Web Animation] CSS3 3D Planetary Movement && Browser Rendering Principle.

After thinking about it, I would like to briefly describe it. I find that every time I review these knowledge points, I gain new insights. This time, I will change to a picture to chrome is an example. The display of a Web page can be thought of as going through the following steps:

  • JavaScript: Generally speaking, we will use JavaScript to achieve some visual changes. . For example, make an animation or add some DOM elements to the page, etc.

  • Style: Calculate style. This process is based on the CSS selector and matches the corresponding CSS style for each DOM element. After this step is completed, it is determined what CSS style rules should be applied to each DOM element.

  • Layout: Layout, the previous step determined the style rules of each DOM element, this step is to specifically calculate the final result of each DOM element The size and position of the display on the screen. The layout of elements in web pages is relative, so changes in the layout of one element will cause changes in the layout of other elements. For example, changes in the width of the 6c04bd5ca3fcae76e30b72ad730ca86d element will affect the width of its child elements, and changes in the width of its child elements will also continue to affect its grandchild elements. Therefore, for browsers, the layout process occurs frequently.

  • Paint: Painting is essentially the process of filling pixels. Including drawing text, color, image, border and shadow, etc., that is, all the visual effects of a DOM element. Generally, this drawing process is done on multiple layers.

  • Composite: The rendering layer is merged. As can be seen from the previous step, the drawing of DOM elements in the page is performed on multiple layers. . After the drawing process is completed on each layer, the browser will merge all the layers into a single layer in a reasonable order and then display it on the screen. This process is especially important for pages with overlapping elements, because once the layers are merged in the wrong order, the elements will display abnormally.

The concept of layer (GraphicsLayer) is involved here again. The GraphicsLayer layer is uploaded to the GPU as a texture. Now it is often You can see that GPU hardware acceleration is closely related to the so-called layer concept. However, it has little relevance to the scrolling optimization of this article. Those who are interested in learning more can google more by themselves.

To put it simply, when a web page is generated, it will be rendered (Layout+Paint) at least once. During the user's visit, it will continue to be reflowed and repainted.

Among them, user scroll and resize behaviors (that is, sliding the page and changing the window size) will cause the page to be continuously re-rendered.

As you scroll, the browser may need to draw some of the pixels in these layers (sometimes called compositing layers). Grouped by elements , when the content of a certain layer changes, we only need to update the structure of the layer, and only redraw and rasterize the changed part of the rendering layer structure , without completely redrawing. Obviously, if something like a parallax website (check me out) moves when you scroll, it's possible to cause large areas of content to resize across multiple layers, which results in a lot of drawing work.

## Debouncing and Throttling

The scroll event itself will trigger the re-rendering of the page. At the same time, the handler of the scroll event will be triggered frequently. Therefore, there should be no complex operations inside the handler of the event, such as DOM operations. Should not be placed in

event handling. In response to such high-frequency triggered event problems (such as page scroll, screen resize, monitoring user input, etc.), the following introduces

two commonly used solutions. Anti-shake and throttling.

防抖(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);

上面简单的防抖的例子可以拿到浏览器下试一下,大概功能就是如果 500ms 内没有连续触发两次 scroll 事件,那么才会触发我们真正想在 scroll 事件中触发的函数。

上面的示例可以更好的封装一下:


// 防抖动函数
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)

防抖函数确实不错,但是也存在问题,譬如图片的懒加载,我希望在下滑过程中图片不断的被加载出来,而不是只有当我停止下滑时候,图片才被加载出来。又或者下滑时候的数据的 ajax 请求加载也是同理。

这个时候,我们希望即使页面在不断被滚动,但是滚动 handler 也可以以一定的频率被触发(譬如 250ms 触发一次),这类场景,就要用到另一种技巧,称为节流函数(throttling)。

节流函数,只允许一个函数在 X 毫秒内执行一次。

与防抖相比,节流函数最主要的不同在于它保证在 X 毫秒内至少执行一次我们希望触发的事件 handler。

与防抖相比,节流函数多了一个 mustRun 属性,代表 mustRun 毫秒内,必然会触发一次 handler ,同样是利用定时器,看看简单的示例:


// 简单的节流函数
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));

上面简单的节流函数的例子可以拿到浏览器下试一下,大概功能就是如果在一段时间内 scroll 触发的间隔一直短于 500ms ,那么能保证事件我们希望调用的 handler 至少在 1000ms 内会触发一次。

 

   使用 rAF(requestAnimationFrame)触发滚动事件

上面介绍的抖动与节流实现的方式都是借助了定时器 setTimeout ,但是如果页面只需要兼容高版本浏览器或应用在移动端,又或者页面需要追求高精度的效果,那么可以使用浏览器的原生方法 rAF(requestAnimationFrame)。

requestAnimationFrame

window.requestAnimationFrame() 这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数。这个方法接受一个函数为参,该函数会在重绘前调用。

rAF 常用于 web 动画的制作,用于准确控制页面的帧刷新渲染,让动画效果更加流畅,当然它的作用不仅仅局限于动画制作,我们可以利用它的特性将它视为一个定时器。(当然它不是定时器)

通常来说,rAF 被调用的频率是每秒 60 次,也就是 1000/60 ,触发频率大概是 16.7ms 。(当执行复杂操作时,当它发现无法维持 60fps 的频率时,它会把频率降低到 30fps 来保持帧数的稳定。

简单而言,使用 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);

上面简单的使用 rAF 的例子可以拿到浏览器下试一下,大概功能就是在滚动的过程中,保持以 16.7ms 的频率触发事件 handler。

使用 requestAnimationFrame 优缺点并存,首先我们不得不考虑它的兼容问题,其次因为它只能实现以 16.7ms 的频率来触发,代表它的可调节性十分差。但是相比 throttle(func, xx, 16.7) ,用于更复杂的场景时,rAF 可能效果更佳,性能更好。

总结一下 

  • 防抖动:防抖技术即是可以把多个顺序地调用合并成一次,也就是在一定时间内,规定事件被触发的次数。

  • 节流函数:只允许一个函数在 X 毫秒内执行一次,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用。

  • rAF:16.7ms 触发一次 handler,降低了可控性,但是提升了性能和精确度。

 

   简化 scroll 内的操作

上面介绍的方法都是如何去优化 scroll 事件的触发,避免 scroll 事件过度消耗资源的。

但是从本质上而言,我们应该尽量去精简 scroll 事件的 handler ,将一些变量的初始化、不依赖于滚动位置变化的计算等都应当在 scroll 事件外提前就绪。

建议如下:

避免在scroll 事件中修改样式属性 / 将样式操作从 scroll 事件中剥离

 

输入事件处理函数,比如 scroll / touch 事件的处理,都会在 requestAnimationFrame 之前被调用执行。

因此,如果你在 scroll 事件的处理函数中做了修改样式属性的操作,那么这些操作会被浏览器暂存起来。然后在调用 requestAnimationFrame 的时候,如果你在一开始做了读取样式属性的操作,那么这将会导致触发浏览器的强制同步布局。

 

   滑动过程中尝试使用 pointer-events: none 禁止鼠标事件

大部分人可能都不认识这个属性,嗯,那么它是干什么用的呢?

pointer-events 是一个 CSS 属性,可以有多个不同的值,属性的一部分值仅仅与 SVG 有关联,这里我们只关注 pointer-events: none 的情况,大概的意思就是禁止鼠标行为,应用了该属性后,譬如鼠标点击,hover 等功能都将失效,即是元素不会成为鼠标事件的 target。

可以就近 F12 打开开发者工具面板,给 6c04bd5ca3fcae76e30b72ad730ca86d 标签添加上 pointer-events: none 样式,然后在页面上感受下效果,发现所有鼠标事件都被禁止了。

那么它有什么用呢?

pointer-events: none 可用来提高滚动时的帧频。的确,当滚动时,鼠标悬停在某些元素上,则触发其上的 hover 效果,然而这些影响通常不被用户注意,并多半导致滚动出现问题。对 body 元素应用 pointer-events: none ,禁用了包括 hover 在内的鼠标事件,从而提高滚动性能。


.disable-hover {
    pointer-events: none;
}

大概的做法就是在页面滚动的时候, 给 6c04bd5ca3fcae76e30b72ad730ca86d 添加上 .disable-hover 样式,那么在滚动停止之前, 所有鼠标事件都将被禁止。当滚动结束之后,再移除该属性。

可以查看这个 demo 页面。

上面说 pointer-events: none 可用来提高滚动时的帧频 的这段话摘自 pointer-events-MDN ,还专门有文章讲解过这个技术:

使用pointer-events:none实现60fps滚动 。

这就完了吗?没有,张鑫旭有一篇专门的文章,用来探讨 pointer-events: none 是否真的能够加速滚动性能,并提出了自己的质疑:

pointer-events:none提高页面滚动时候的绘制性能?

结论见仁见智,使用 pointer-events: none 的场合要依据业务本身来定夺,拒绝拿来主义,多去源头看看,动手实践一番再做定夺。

其他参考文献(都是好文章,值得一读):

  • 实例解析防抖动(Debouncing)和节流阀(Throttling)

  • 无线性能优化:Composite

  • Javascript高性能动画与页面渲染

  • Google Developers--渲染性能

  • Web高性能动画

 

The above is the detailed content of High-performance scroll and page rendering optimization. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn