RAIL は Web ページをよりスムーズにします: Web パフォーマンスの最適化

パフォーマンスの最適化

パフォーマンスの最適化といえば、最初のバイト時間、ホワイト スクリーン時間、ファースト スクリーン時間、ユーザー インタラクティブ時間、DOMContentLoaded 時間、onLoad 時間など、ロードに関連する時間がすぐに思い浮かぶかもしれませんが、人によって異なる場合があります。白画面の時間に注目する人もいれば、最初の画面の時間に注目する人もいるなど、測定基準が異なるため、完全に一致するわけではありません。もちろん、DOM レンダリング、60FPS アニメーション、ベンチマークなど、パフォーマンスの最適化の他の側面も考慮することができますが、いつ最適化を行うべきでしょうか? いつもやってますか?全部する?これは少し非現実的かもしれません。

私たちのほとんどは、最適化作業に専念する十分な時間を持っていません。どの重要なものを最適化し、どれを二次的なものにする必要があるかを示す信頼できる基準が必要です。

この点に関して、Chrome チームはユーザー中心のパフォーマンス モデルRAILを提案しました。

RAILについて詳しく説明する前に、なぜパフォーマンスの最適化を行うのかを思い出してください。実際、それは「遅い」という言葉にすぎません。

DOM操作が遅い?ウェブページの読み込みが遅い?<head> に <script> をロードするのが遅いですか? JavaScript アニメーションは遅いですか? 20msの動作は遅い?では、0.5 秒または 10 秒はどうでしょうか。...

明らかに、操作ごとに必要な時間の大きさは異なります。この操作のコンテキストなしに速度について話すことは無意味です。数十キロバイトのファイルを読み込んでいる場合、ユーザーにとって0.5秒は問題にならないかもしれませんが、応答に0.5秒かかるタップ操作であれば、間違いなく機能しません。

では、「何が遅いのか?」というのは、実際にユーザーがこの操作についてどう感じているかということです。

これは、RAIL が「ユーザーにフォーカスする」と呼んでいるものです。

実際、ユーザーがサイトにアクセスすると、通常は次のような行動をとります。

  • クリック

  • リソースの読み込みを待機しています

  • アニメーションを見る

  • ページをスクロール

  • ...

Chrome チームは、これらの動作をレスポンス、アニメーション、アイドル、ロードの 4 つのカテゴリに分類し、上記の 4 つの単語の頭文字に由来する名前の RAIL と呼ばれるユーザー中心のパフォーマンス モデルを提案し、目標を指定します。ボタンをクリックした後にフィードバックを与えると、ユーザーはスムーズに感じます。

RAIL では、パフォーマンスに影響を与える動作を、Response (応答)、Animation (アニメーション)、Idle (アイドル)、Load (読み込み) の 4 つの側面に分けています。そうです、RAILという名前は覚えやすいこの4つの言葉の頭文字から来ています。

1.1 対応

調査によると、ユーザーの入力操作に100 ミリ秒以内に応答することは、通常、人間による即時応答と見なされます。どれだけ時間がかかったとしても、操作と反応のつながりが途切れてしまい、操作が遅れているように感じてしまいます。たとえば、ユーザーがボタンをクリックして100 ミリ秒以内に応答が返された場合、ユーザーは応答が非常にタイムリーであると感じ、わずかな遅延も感じません。

1.2 アニメーション

最近のほとんどのデバイスの画面リフレッシュ レートは 60Hz、つまり、画面は 1 秒間に 60 回更新されるため、Web ページのアニメーションの実行速度が 60FPS に達している限り、アニメーションは滑らかに感じられます。

F (Frames) P (Per) S (Second) は 1 秒あたりに送信されるフレーム数を指し、60FPS は 1 秒あたり 60 フレームを指し、各フレームは換算でほぼ 16 ミリ秒です。

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

しかし通常、ブラウザーは各フレームのコンテンツを画面に描画するのに時間を費やす必要があるため (スタイルの計算、レイアウト、描画、合成などを含む)、通常は JS コードを実行するのに 10 ミリ秒しかありません。

1.3 アイドル

パフォーマンスを向上させるために、通常はブラウザーのアイドル期間 (Idle Period) をフルに活用して、優先度の低い処理を実行します。たとえば、次に使用される可能性のあるデータを事前にリクエストしたり、アイドル期間中に分析データをレポートしたりします。

RAIL では、アイドル期間に実行されるタスクは50msを超えてはならないことが規定されています.もちろん、RAIL の規則だけでなく、W3C Performance Working Group の Longtasks 標準でも、 50msを超えるタスクは長いタスクであると規定されています.から?

ブラウザはシングル スレッドです, つまり, メイン スレッドは同時に 1 つのタスクしか処理できません. タスクに時間がかかりすぎると, ブラウザは他のタスクを実行できなくなります.応答がありません。

100 ミリ秒以内に応答するために、アイドル期間に実行されるタスクを50 ミリ秒に制限するということは、アイドル タスクが開始されたばかりのときにユーザーの入力動作が発生したとしても、ブラウザーは Creates の代わりにユーザー入力に応答するためにまだ 50 ミリ秒残っていることを意味します。ユーザーが認識できる遅延。図 1-1 に示すように:

写真1-1

実際、アイドル タスクであろうと他の優先度の高いタスクであろうと、実行時間は 50ms を超えてはなりません。

1.4 ロード

Web ページを読み込んで、ユーザーに1 秒以内にコンテンツを表示させないと、ユーザーの気を散らしてしまいます。ユーザーは自分がやろうとしていることが中断されたように感じ、10 秒以内にWeb ページを開くことができなければ、ユーザーはがっかりしてやりたいことをあきらめ、二度と戻ってこないかもしれません。

1.5 まとめ

RAIL を使用すると、Web ページが滑らかであるかどうかを確認できます。RAIL では、ユーザーの知覚の観点からいくつかの指標が規定されており、この基準を満たしている限り、Web ページは滑らかであり、ユーザーは Web ページが滑らかであると感じます。

RAIL | 主な指標 | ユーザー アクション ---- | --------- | ------------- 応答 | 100 ミリ秒未満 | ボタンをクリックします。アニメーション (アニメーション) | 16ms 未満 | ページのスクロール、指のドラッグ、アニメーションの再生など。Idle (Idle) | 50 ミリ秒未満 | ユーザーはページを操作しませんが、メイン スレッドは次のユーザー入力を処理するのに十分なはずです。ロード | 1000 ミリ秒 | ユーザーがページをロードしてコンテンツを表示します。

2. ピクセル パイプライン

ピクセル パイプラインは、滑らかな Web ページを作成するための魂であり、後で紹介するテクノロジはすべてそれに関連しています。

上の図はピクセル パイプラインです. 通常、JS を使用していくつかのスタイルを変更すると、ブラウザがスタイルの計算を実行し、レイアウト、描画、最後にすべてのレイヤーをマージして、レンダリング プロセス全体を完了します. この期間中、各ステップは、ページがフリーズする可能性があります。

すべてのスタイル変更がこれらの 5 つの手順を実行する必要があるわけではないことに注意してください。例: 要素の幾何学的プロパティ (幅、高さなど) が JS で変更された場合、ブラウザは 5 つの手順すべてを実行する必要があります。ただし、テキストの色を変更するだけの場合は、下の図に示すように、レイアウト (レイアウト) をスキップできます。

最終的な合成を除いて、最初の 4 つのステップはさまざまなシナリオでスキップできます。例: CSS アニメーションは JS 操作をスキップでき、JS を実行する必要はありません。

css-triggers は、さまざまな CSS プロパティが変更されたときにピクセル パイプラインのどのステップがトリガーされるかを示します。

簡単に言えば、ピクセル パイプラインのステップ数が多いほどレンダリング時間が長くなり、1 つのステップにも何らかの理由で長い時間がかかる場合があるため、ステップ数が多いか、1 つのステップに時間がかかるかは、最終的には異なります。どちらも全体のレンダリング時間が長くなります。全体の時間が長いほど、RAIL が規定する指標を超える可能性が高くなります。

簡単な例を挙げると、Web アニメーションのレンダリングが 60FPS に達すると、アニメーションはフレームをドロップしません。レンダリング パイプラインのレイアウトと描画に 10 ミリ秒かかると仮定し、スタイルの計算と合成の時間を追加すると、JS がアニメーションを処理するために残された時間はわずか数ミリ秒です.JS の実行が数ミリ秒を超えると、アニメーションが各フレームを消費する時間は 16 ミリ秒を超えます.このとき、アニメーションは確実にフレームをドロップし、ユーザーは肉眼で明らかなフリーズを確認できます.

当然,即便能保证每一帧的总耗时小于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

おすすめ

転載: blog.csdn.net/coinisi_li/article/details/128813162