RAIL makes web pages smoother: web performance optimization

performance optimization

When it comes to performance optimization, you may immediately think of the time related to loading, such as first byte time, white screen time, first screen time, user interactive time, DOMContentLoaded time, onLoad time, etc., but different people There may be different measurement standards. For example, some people pay more attention to the time of white screen, and some pay more attention to the time of first screen, etc. This is not completely consistent. Of course, we may also consider other aspects of performance optimization, such as DOM rendering, 60FPS animation, benchmarks, etc., but when should we do optimization? Do it all the time? Do all? This might be a bit impractical.

Most of us don't have enough time to devote to optimization work, we need an authoritative standard to tell us which important things must be optimized and which are secondary.

In this regard, the Chrome team proposed a user-centric performance model: RAIL .

Before we go deep into RAIL , let's recall, why do we do performance optimization? In fact, it is nothing more than one word, "slow"!

A DOM operation is slow? A web page loading slowly? Loading a <script> in <head> is slow? JavaScript animations are slow? A 20ms operation is slow? So what about 0.5s or 10s? ...

Obviously, different operations require different orders of magnitude of time, and it is meaningless to talk about speed without the context of this operation. When you are loading a file of tens of kilobytes, 0.5s may not be a problem for users, but if it is a tap operation that takes 0.5s to respond, then it will definitely not work.

So "what is slow?" is actually how users feel about this operation. After all, our site is built for users.

This is what RAIL calls "Focus On The User".

In fact, when a user visits a site, he usually has the following behaviors:

  • click

  • Waiting for resources to load

  • watch animation

  • scroll page

  • ...

The Chrome team divides these behaviors into 4 categories: Response, Animation, Idle, Load, and proposes a user-centered performance model called RAIL, whose name comes from the initials of the above 4 words; and specifies a Goals, e.g. how long to give feedback after clicking a button will feel smooth to the user.

RAIL divides the behaviors that affect performance into four aspects, namely: Response (response), Animation (animation), Idle (idle) and Load (loading). That's right, the name RAIL comes from the initials of these four words, which is easy to remember.

1.1 Response

Research shows that responding to a user's input operation within 100ms is usually considered an immediate response by humans. No matter how long it takes, the connection between operation and reaction will be broken, and people will feel that its operation is delayed. For example: when a user clicks a button, if a response is given within 100ms , the user will feel that the response is very timely and will not feel the slightest delay.

1.2 Animation

The screen refresh rate of most devices nowadays is 60Hz, that is, the screen refreshes 60 times per second; therefore, as long as the running speed of web page animation reaches 60FPS, we will feel that the animation is smooth.

F (Frames) P (Per) S (Second) refers to the number of frames transmitted per second, 60FPS refers to 60 frames per second; each frame is almost 16 milliseconds in conversion.

(1 秒 = 1000 毫秒) / 60 帧 = 16.66 毫秒/帧

But usually the browser needs to spend some time drawing the content of each frame to the screen (including style calculation, layout, drawing, compositing, etc.), so usually we only have 10 milliseconds to execute JS code.

1.3 Idle

For better performance, we usually make full use of the browser idle period (Idle Period) to do some low-priority things. For example: pre-request some data that may be used next or report analysis data during the idle period.

RAIL stipulates that tasks running in an idle period must not exceed 50ms . Of course, not only RAIL regulations, but the Longtasks standard of the W3C Performance Working Group also stipulates that tasks exceeding 50ms are long tasks. So how does the number 50ms come about?

The browser is single-threaded, which means that the main thread can only process one task at the same time. If a task takes too long, the browser cannot perform other tasks, and the user will feel that the browser is stuck because his Input does not get any response.

In order to give a response within 100ms , limiting the tasks executed in the idle period to 50ms means that even if the user's input behavior occurs when the idle task is just started, the browser still has 50ms left to respond to user input instead of Creates a user-perceivable delay. As shown in Figure 1-1:

Picture 1-1

In fact, whether it is an idle task or other high-priority tasks, the execution time must not exceed 50ms .

1.4 Loading

Failure to load a web page and get the user to see the content within 1 second will distract the user. The user will feel that what he is going to do is interrupted. If the webpage cannot be opened within 10 seconds , the user will be disappointed and will give up what they want to do, and they may not come back in the future.

1.5 Summary

With RAIL, we can tell if our web pages are silky smooth. RAIL stipulates some indicators from the perspective of user perception. As long as our webpage meets the standards, our webpage will be silky smooth, and users will feel that our webpage is smooth.

RAIL | Key Indicators | User Actions ---- | --------- | ------------- Response | Less than 100ms | Click a button. Animation (Animation) | Less than 16ms | Scrolling pages, dragging fingers, playing animations, etc. Idle (Idle) | Less than 50ms | The user does not interact with the page, but the main thread should be sufficient to handle the next user input. Load | 1000ms | The user loads the page and sees the content.

2. Pixel Pipeline

The pixel pipeline is the soul of making silky web pages, and the technologies we will introduce later are all related to it.

The picture above is the pixel pipeline. Usually we use JS to modify some styles, and then the browser will perform style calculations, then layout, draw, and finally merge all layers together to complete the entire rendering process. During this period, each step has May cause page freezes.

Note that not all style changes need to go through these five steps. For example: If the geometric properties (width, height, etc.) of an element are modified in JS, then the browser needs to go through all five steps. But if you just modify the color of the text, the layout can be skipped, as shown in the figure below:

Except for the final compositing, the first four steps can be skipped in different scenarios. For example: CSS animation can skip JS operation, it does not need to execute JS.

css-triggers gives which steps of the pixel pipeline are triggered when different CSS properties are changed.

Simply put, the more steps the pixel pipeline goes through, the longer the rendering time will be, and a single step may also take a long time for some reason; so whether there are many steps or a single step takes a long time, eventually Both lead to longer overall rendering times. The longer the overall time, the more likely it will exceed the indicators stipulated by RAIL.

To give a simple example: if the rendering of web animation reaches 60FPS, the animation will not drop frames. Assuming that the layout and drawing of the rendering pipeline takes 10ms, then adding the time of style calculation and synthesis, the time left for JS to process the animation is only a few milliseconds. If the execution of JS exceeds a few milliseconds, then the animation consumes each frame The time will exceed 16ms. At this time, the animation will definitely drop frames, and the user can see obvious freezes with the naked eye.

当然,即便能保证每一帧的总耗时小于16ms,依然无法保证不会丢帧。关于这点后面我们会详细介绍。

3. 如何让动画更丝滑

动画需要达到60FPS才能变得丝滑,本节我们介绍如何让动画在不丢帧的情况下稳定保持在60FPS。

3.1 使用Chrome开发者工具测量动画性能

在评估动画性能时,通常需要逐帧评估像素管道的开销;使用 Chrome 开发者工具可以辅助我们进行精准的测量。

在Chrome开发者工具中,点击Performance面板,然后选中Screenshots复选框,。如图3-1所示:

图3-1Chrome开发者工具Performance面板

然后点击录制按钮,录制完毕后点击停止按钮就可以捕获当前页面的性能数据。如图3-2所示:

图3-2捕获性能数据

捕获出的结果如图3-3所示:

图3-3捕获出的性能结果

我们可以放大主线程从而精准的看到每一帧浏览器都执行了哪些任务以及每个任务耗费了多长时间。如图3-4所示:

图3-4性能面板最主要的部分

从上图可以看到,浏览器每一帧渲染所执行的任务与前面我们介绍的像素管道是相同的。上图中因为是CSS动画,所以没有运行JS,但每一帧都需要计算样式、布局、绘制与合成。

3.2 如何让JS动画更丝滑

JS动画是使用定时器不停的执行JS,通过在JS中修改样式完成网页动画;若想保证动画流畅,从JS的执行到最终浏览器显示出画面,每一帧总耗时最多16ms,这样动画才能达到60FPS。

如图3-4所示,即便是在不执行JS的情况下,浏览器计算样式、布局、绘制等工作也是需要时间的,所以需要给浏览器预留出 充分的时间(6ms) 做这些事情,现在留给JS的执行时间就只有 10ms

图3-5每一帧总体耗时必须小于16ms,JS运行时间小于10ms

一旦JS运行时间超过10ms,就很有可能导致这一帧的像素管道整体耗时超过16ms,从而无法达到60FPS,但你以为只要保证JS的运行时间小于10ms就一定能保证不丢帧?Naive~

3.2.1 使用requestAnimationFrame

即便你能保证每一帧的总耗时都小于16ms,也无法保证一定不会出现丢帧的情况,这取决于触发JS执行的方式。

假设使用 setTimeout 或 setInterval 来触发JS执行并修改样式从而导致视觉变化;那么会有这样一种情况,因为setTimeout 或 setInterval没有办法保证回调函数什么时候执行,它可能在每一帧的中间执行,也可能在每一帧的最后执行。所以会导致即便我们能保障每一帧的总耗时小于16ms,但是执行的时机如果在每一帧的中间或最后,最后的结果依然是没有办法每隔16ms让屏幕产生一次变化。如图3-6所示:

图3-6使用定时器触发动画

也就是说,即便我们能保证每一帧总体时间小于16ms,但如果使用定时器触发动画,那么由于定时器的触发时机不确定,所以还是会导致动画丢帧。现在整个Web只有一个API可以解决这个问题,那就是requestAnimationFrame,它可以保证回调函数稳定的在每一帧最开始触发。如图3-7所示:

图3-7使用requestAnimationFrame触发动画

3.2.2 避免FSL

FSL (Forced Synchronous Layouts) 被称为强制同步布局;前面介绍像素管道时说过,将一帧送到屏幕会通过如下顺序:

先执行JS,然后在JS中修改了样式从而导致样式计算,然后样式的改动触发了布局、绘制、合成。但JavaScript可以强制浏览器将布局提前执行,这就叫 F (强制) S (同步) L (布局) 。

图3-8强制同步布局

通常我们一不小心就造成了FSL,请看下面代码:

box.classList.add('big');constwidth=box.offsetWidth;

代码中通过新增class修改了元素的样式,随后使用offsetWidth读取元素的宽度。乍一看似乎没什么问题,但这段代码会导致FSL。

在 JavaScript 运行时,上一帧已经渲染好的所有布局值都是已知的,我们可以使用offsetWidth这样的语法获得值;但这一帧刚修改完的样式浏览器还没渲染呢,这时候使用offsetWidth这样的语法读取元素的宽度,那么浏览器为了告诉我们宽度值,它必须先计算该宽度,这就需要布局。如图3-8所示,布局跑到了样式计算的前面。

所以正确的做法是先获取宽度,然后再更改样式:

constwidth=box.offsetWidth;box.classList.add('big');

看起来,似乎即使触发了FSL也不过就是管道的顺序变了而已,影响好像并没有那么大。

单个FSL对性能的影响确实不大,但如果触发了布局抖动,则影响会变得非常大。看下面代码:

constcontainer=document.querySelector('.container');constboxes=document.querySelectorAll('p');for(vari=0;i<boxes.length;i++){// Read a layout property
constnewWidth=container.offsetWidth;// Then invalidate layouts with writes.
boxes[i].style.width=newWidth+'px';}

上面代码的作用是批量修改N个P元素的宽度;在循环中我们先获取容器元素的宽度,随后设置了P元素的样式。这会导致浏览器去布局,然后计算样式。每次更改样式,都会导致刚刚执行的布局失效,因为我们又改了新的样式,所以下一轮循环读取宽度时,浏览器又要执行一次布局,如此反复直到循环结束。在循环期间,浏览器不停地执行无效布局,这被称为 布局抖动(Layout Thrashing);这种错误导致的性能问题非常高。

如果我们不小心触发了FSL,Chrome开发者工具会给出红色的线提示,如图3-9所示:

图3-9开发者工具提示FSL

同时任务的右上角会有红色的三角形表示,我们可以放大任务进一步查看,如图3-10所示:

图3-10开发者工具提示FSL详情

若想看Demo可以点击我,在Demo中点击按钮可以让P标签的宽度变长。

为了避免布局抖动,我们可以将读取元素宽度的代码放到循环的外面。代码如下:

constcontainer=document.querySelector('.container');constboxes=document.querySelectorAll('p');// Read a layout property
constnewWidth=container.offsetWidth;for(vari=0;i<boxes.length;i++){// Then invalidate layouts with writes.
boxes[i].style.width=newWidth+'px';}

若想看Demo可以点击我,可以看到这个Demo与前一个demo一模一样,甚至我们无法用肉眼分辨出哪个更快,这是因为DOM元素少,所以总体时间都比较少,但我们可以通过Chrome开发者工具来捕获性能数据。

图3-11优化后的时间

图3-11可以看到,优化后这一帧的总时间用了4.7ms,而优化前的是101ms,如图3-12所示:

图3-12优化前的时间

优化后比优化前,每帧所耗费的时间快了21.7倍,数字非常惊人。

3.3 如何让CSS动画更丝滑

CSS动画通常使用@keyframe或transition结合样式的变动来实现视觉变化的效果。我们同样可以通过减少像素管道的步骤和每个步骤所耗费的时间让CSS动画更流畅。

本节介绍的CSS动画的优化方式同样适用于JS动画,但上一节介绍的JS动画优化方法不适用于CSS动画,它们是包含关系。

绘制(Paint)通常需要花费很长时间,我们可以通过Chrome开发者工具来观察正在绘制的区域。打开开发者工具,按下键盘上的 Esc 键。在出现的面板中,切换到“rendering”标签,然后选中“Paint flashing”。如图3-13所示:

图3-13开启绘制闪烁

开启绘制闪烁(Paint flashing)后,每当页面发生绘制时,我们都可以在屏幕上看到绘制发生区有绿色在闪烁。如图3-14所示:

图3-14绘制区域闪烁

如图3-14所示,当我们开启了绘制闪烁,则会绘制区域出现了绿色的闪烁,可以点击我查看Demo

当我们看到我们认为不应该绘制的区域时,我们应该进一步研究并取消绘制区域。

如何才能避免绘制的发生呢?答案是:图层。

事实上浏览器在渲染页面时,可以将页面分为很多个图层,有点类似于PhotoShop,一张图片在PotoShop中是由多个图层组合而成,而浏览器最终显示的页面实际也是由多个图层构成的。如图3-15所示:

图3-15图层

将原本不断发生变化的元素提升到单独的图层中,就不再需要绘制了,浏览器只需要将两个图层合并在一起即可,查看Demo请狠狠的点击我

如果您点击了上面的Demo地址,并开启了绘制闪烁,您会发现没有任何闪烁发生,因为浏览器没有进行绘制。如果您查看Layers面板,你会看到这样的场景,如图3-16:

3-16图层

当我们使用Performance面板捕获性能数据时会发现绘制(Paint)已经不见了。如图3-17所示:

图3-17捕获不到绘制

创建图层的最佳方式是使用will-change,但某些不支持这个属性的浏览器可以使用3D 变形(transform: translateZ(0))来强制创建一个新层。

在Chrome开发者工具“rendering”标签中,选中“Layer borders”。可以看到页面中有哪些合成层。合成层会使用橘黄色的边框,如图3-18所示:

图3-18显示合成层

为了减少绘制,可以通过新增图层,但是图层的管理也是需要成本的,所以要避免滥用,通常需要具体情况具体分析,做出合适的选择。

前面我的Demo都是修改元素的left属性让方块移动,这避免不了需要进行布局操作,最佳的方法是使用transform属性,这个属性是由合成器单独处理的,所以使用这个属性可以避免布局与绘制。

总结

RAIL可以帮助我们判断什么样的网页是丝滑的,而开发者工具可以让我们进一步准确的捕获出网页的性能数据。

JS动画要保证预留出6ms的时间给浏览器处理像素管道,而自身执行时间应该小于10ms来保证整体运行速度小于16ms。但触发动画的时机也很重要,定时器无法稳定的触发动画,所以我们需要使用requestAnimationFrame触发JS动画。同时我们应该避免一切FSL,它对性能的影响非常大。

CSS动画我们可以通过降低绘制区域并且使transform属性来完成动画,同时我们需要管理好图层,因为绘制和图层管理都需要成本,通常我们需要根据具体情况进行权衡并做出最好的选择。

参考文章

https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/rail?hl=en

https://zhuanlan.zhihu.com/p/66398148

Guess you like

Origin blog.csdn.net/coinisi_li/article/details/128813162