ホームページ  >  記事  >  ウェブフロントエンド  >  レンダリングのパフォーマンス_html/css_WEB-ITnose

レンダリングのパフォーマンス_html/css_WEB-ITnose

WBOY
WBOYオリジナル
2016-06-24 11:15:05939ブラウズ

この記事は 3 日前に github に公開されたものです。質問がある場合は、github に言及してください

ほとんどのユーザーはリソースの読み込み後のパフォーマンスに焦点を当てています。アプリケーションがロードされます。それは特定の用途です。したがって、特にワイヤレス側でユーザーに迅速に対応するには、ブラウザーのレンダリング パフォーマンスを理解する必要があります。

RAIL パフォーマンス モデル

まず第一に、考慮する必要がある質問があります。どのような Web サイトがスムーズですか?次のような一般的な感覚を与えることができるかもしれません: 第 2 レベルの応答など。実際、ユーザーがスムーズだと感じる Web サイトはスムーズである、という非常にお世辞のような答えもできます。ほとんどすべての Web サイトはユーザーをページに留まらせたいと考えているため、ユーザーを中心としたパフォーマンス モデルを構築する必要があります。以下は、Google が提案したユーザー中心のパフォーマンス モデルです。この種のデータは初めてのものではありません (例: ユーザーに応答するには 100 ミリ秒が非常に適切です)。 。

上の図は RAIL の具体的な意味です。 以下にいくつかの主要なデータ インジケーターを示します。

  • 応答: 通常、この時間を超える場合、ウィンドウはユーザーに応答する必要があります。ユーザーは遅れを感じるでしょう。

  • アニメーション: 0~16 ミリ秒、画面は 1 秒あたり 60 回更新され、16 ミリ秒は各フレームの時間を表します。ユーザーはアニメーションに細心の注意を払っており、アニメーションのフレームが失われるとユーザーは簡単に気づきます。したがって、アニメーションは通常 60FPS で制御する必要があります。

  • アイドル: メインプロセスのアイドル時間を最大化し、ユーザー入力にタイムリーに応答できるようにします。

  • ロード: コンテンツは 1000 ミリ秒以内にロードする必要があります。1000 ミリ秒を超えると、ロードが遅くなります。

上記のパフォーマンス モデルを達成するには、アプリケーションはどのような側面から始める必要がありますか?ブラウザーがどのようにページをレンダリングするかを知っていて、レンダリング プロセスの主要な手順を最適化できれば、半分の労力で 2 倍の結果を得ることができるでしょうか?

クリティカル レンダリング パス

上の図はブラウザ レンダリングのクリティカル パスです。まず、ブラウザがページを解析することから始めましょう。

  • 変換: ブラウザは、ディスクまたはネットワークから HTML の元のバイトを読み取り、対応するエンコード仕様 (現在は通常 utf-8) に従って元のファイルをデコードします。

  • 記号化: W3C 標準に従って、対応する記号 (通常は山括弧内) に変換されます。

  • DOM 構築: HTML パーサーは、CSS または JS に遭遇すると、tag タグを解析し、トークンを生成し、対応するリクエストを送信します。 HTML は解析時にメイン プロセスをブロックしますが、CSS は一般にメイン プロセス (メディア クエリを除く) をブロックします。つまり、解析プロセス中に応答できないことになります。 JS は、手動で async を追加した後に非同期読み込みを実現し、トークンに基づいて対応する DOM ツリーを生成します。

  • CSSDOM の構築、CSS スタイルを追加して CSSDOM ツリーを生成します。

  • レンダリング ツリーの構築は DOM ツリーのルート ノードから開始され、表示されている各ノードを走査し、表示されている各ノードに対応する一致する CSSOM ルールを見つけて、そのコンテンツと計算されたスタイルとともにこれらのルールを適用します。

  • スタイル計算。ブラウザはすべての相対位置を絶対位置に変換し、一連のスタイル計算を実行します。

  • レイアウト、ブラウザーの位置とレイアウト要素。

  • 描画、要素のスタイル、色、背景、サイズ、境界線などを描画します。

  • コンポジット、レイヤーを結合して画面上に表示します。

アニメーションを作成している場合、通常は JS を使用して対応するスタイルを変更します。その後、ブラウザーは JS の実行、スタイルの計算、レイアウト、描画、合成などの多くの重要な手順を実行します (これについては後で説明します) このステップは実際には長くなる場合も短くなる場合もあります)。次に、実行すべき最適化は、これらのステップで最適化し、中間の時間のかかるステップを削除することです。

JavaScript の実行を最適化する

上の図で説明されている 4 つのシナリオは、ユーザー入力またはアニメーションへの応答に影響を与える可能性があります。関数入力イベント処理、タイミングの悪い JS、長時間にわたる JS 実行、ガベージ コレクション。

関数の入力イベント処理

まず最初に知っておく必要があるのは、ブラウザは複数の処理プロセス (Compositor、Tile Worker、Main) で構成されているということです。ユーザーがスクロールなどの入力操作 (スクロール、クリックなど) を実行すると、Compositor プロセスはこのイベントを受け取ります (実際には、任意のユーザー入力イベントを受け入れることができます)。可能であれば、メイン プロセスには通知せず、直接処理します。言う:ロール、さあ、赤ちゃん牛。ということで、ページがスクロールしました。もちろん、これには、レイヤーの位置を更新することや、メインスレッドがアイドル状態である間に GPU にフレームを描画させることも含まれます。ただし、そうでないこともよくあります。入力イベントが JS 処理イベントにバインドされている場合、Compositor プロセスはメイン プロセスを積極的にスキップできません。

上の図に示すように、JS によるイベントの処理に時間がかかりすぎる場合、JS の処理が完了するまで入力イベントへの応答はブロックされたままになります。応答が 100 ミリ秒を超えると、ユーザーは遅延を経験します。したがって、ユーザー イベントを処理するときは、次のようにする必要があります:

  • 長時間にわたる JS の実行を避けます。

  • 処理中にスタイルを変更しないでください。スタイルを変更すると、その後のレイアウト、描画、構成、その他の操作が発生するためです。

  • ユーザー入力をデバウンスします。

最適化処理

その他の最適化:

  • setTimeout 時間制御によりフレームの途中が発生する可能性があるため、 requestAnimationFrame を使用して、 requestAnimationFrame をより適切にサポートします。

  • Web ワーカーを使用して複雑な計算された JS を処理するには、Web ワーカーを使用します。

  • ガベージ コレクションを削減します。ガベージ コレクションの時間が制御されないため、アニメーションの実行がループされる方が理想的です。オブジェクトを再利用します。

スタイル計算

DOM 要素の追加または削除、要素属性とスタイル クラスの変更、アニメーション効果の適用などを行うと、DOM 構造が変更されるため、ブラウザは各要素のスタイルを再計算する必要があります。 . 、ページまたはその一部を再レイアウトします (ほとんどの場合)。スタイルを計算する最初のステップは、一致するスタイル セレクターのセットを作成することです。ブラウザはこれを利用して要素にスタイルを適用します。 2 番目のステップでは、一致するスタイル セレクターに基づいて、対応する特定のスタイル ルールを取得し、DOM 要素に適用される最終的な特定のスタイルを計算します。したがって、スタイルの最適化も 2 つのステップです:

セレクターの複雑さを軽減する

セレクターの複雑さを軽減するにはどうすればよいですか?

.box:nth-last-child(-n+1) .title {  /* styles */}.final-box-title {  /* styles */}

上記のコードはすべて同じ要素を選択します。多数の要素がある場合、2 番目のセレクターのパフォーマンスは最初のセレクターよりも大幅に向上します。 BEM 仕様でも同様のことが行われており、特性に従ってセレクターによって要素を直接選択するパフォーマンスが優れていることがよくあります。

スタイルの計算量を削減する

要素の計算量は変更される要素の数に比例するため、無効な要素を減らすことだけに注意する必要があります。

<div>  <div>    <p>多层无意义的标签</p>  </div></div>

上の例のように、冗長なタグが作成される場合があります。外側のスタイルを変更する場合、冗長なタグもスタイル計算する必要があるため、パフォーマンスが無駄になります。

Layout

ブラウザが DOM 要素の幾何学的情報 (ページ上のサイズと位置) を計算するプロセス。各要素には、CSS プロパティの設定、要素自体のコンテンツのサイズ、または親要素のサイズによって決定される、明示的または暗黙的なサイズ情報があります。 Blink/WebKit ベースのブラウザおよび IE では、このプロセスはレイアウトと呼ばれます。 Firefox などの Gecko ベースのブラウザでは、このプロセスはリフローと呼ばれます。

レイアウトのトリガーを避ける

現時点では、変換と不透明度は合成のみを引き起こし、レイアウトや再描画は引き起こしません。プロセス全体のうち、パフォーマンスを消費するレイアウトや描画プロセスが直接スキップされるため、パフォーマンスは明らかに非常に優れています。他の CSS プロパティの変更によって引き起こされるプロセスは異なり、一部のプロパティはレイアウトをスキップします。詳細については、「CSS トリガー」を参照してください。したがって、最適化の最初のステップは、レイアウトのトリガーをできる限り回避することです。

Flexbox レイアウトを使用します

Flexbox レイアウト スキームのパフォーマンスは以前のレイアウト スキームよりも向上しており、現在のブラウザーの Flexbox サポートは非​​常に高くなっています:

レイアウト イベントの強制的な同期を避ける

最初JS スクリプトを実行し、スタイルの計算、レイアウトの順に実行します。ただし、JS スクリプトを実行する前にブラウザにレイアウト プロセスを強制的に実行させることもできます。これは強制同期レイアウトと呼ばれます。 JS スクリプトの実行時に取得できる要素のスタイル属性値はすべて前のフレームからのものであり、すべて古い値です。したがって、このフレームの先頭にある要素の高さ属性を読み取りたい場合は、次のような JS コードを書くことができます:

function logBoxHeight() {  box.classList.add('super-big');  // Gets the height of the box in pixels  // and logs it out.  console.log(box.offsetHeight);}

为了给你返回 box 的 height 属性值,浏览器必须首先应用 box 的属性修改(因为对其添加了 super-big 样式),接着执行布局过程。在这之后,浏览器才能返回正确的 height 属性值。这样就造成了同步布局事件,是非常消耗性能的。大多数情况下,你应该都不需要先修改然后再读取元素的样式属性值,使用上一帧的值就足够了。过早地同步执行样式计算和布局是潜在的页面性能的瓶颈之一。

function logBoxHeight() {  // Gets the height of the box in pixels  // and logs it out.  console.log(box.offsetHeight);  box.classList.add('super-big');}

避免快速连续的布局

还有一种情况比强制同步布局更糟:连续快速的多次执行它。

function resizeAllParagraphsToMatchBlockWidth() {  // Puts the browser into a read-write-read-write cycle.  for (var i = 0; i < paragraphs.length; i++) {    paragraphs[i].style.width = box.offsetWidth + 'px';  }}

上述代码对一组段落标签执行循环操作,设置 p 标签的width属性值,使其与 box 元素的宽度相同。看上去这段代码是没问题的,但问题在于,在每次循环中,都读取了 box 元素的一个样式属性值,然后立即使用该值来更新 p 元素的 widt h属性。在下一次循环中读取 box 元素 offsetwidth 属性的时候,浏览器必须先使得上一次循环中的样式更新操作生效,也就是执行布局过程,然后才能响应本次循环中的样式读取操作。布局过程将在每次循环中发生。优化代码:

// Read.var width = box.offsetWidth;function resizeAllParagraphsToMatchBlockWidth() {  for (var i = 0; i < paragraphs.length; i++) {    // Now write.    paragraphs[i].style.width = width + 'px';  }}

如果你想确保编写的读写操作是安全的,你可以使用 FastDOM。它能帮你自动完成读写操作的批处理,还能避免意外地触发强制同步布局或快速连续的布局。

绘制

提升移动或渐变元素的绘制层

绘制并非总是在内存中的单层画面里完成的。实际上,浏览器在必要时将会把一帧画面绘制成多层画面,然后将这若干层画面合并成一张图片显示到屏幕上。通过渲染层提升可以减小绘制区域,我们可以用调试工具查看到绘制层:

在页面中新建一个渲染层最好的方式就是使用 will-change 属性,同时再与 transform 属性一起使用,就会创建一个新的组合层:

.element {  will-change: transform;}

对于那些目前还不支持 will-change 属性、但支持创建渲染层的浏览器,可以使用一个 3D transform 属性来强制浏览器创建一个新的渲染层:

.element {  transform: translateZ(0);}

注意: 别盲目创建渲染层,一定要分析其实际性能表现。因为创建渲染层是有代价的,每创建一个新的渲染层,就意味着新的内存分配和更复杂的层的管理。并且在移动端 GPU 和 CPU 的带宽有限制,创建的渲染层过多时,合成也会消耗跟多的时间。

仔细规划动画和简化绘制的复杂度

有时候,尽管把元素提升到了一个单独的渲染层,浏览器会把两个相邻区域的渲染任务合并在一起进行,这将导致整个屏幕区域都会被绘制。所以可以使用调试工具查看,仔细规划动画。不同的 CSS 属性绘制的成本是不一样的,绘制一个阴影就比绘制边框更费时。当然,这个浏览器也在不停优化中,现在的耗时渲染属性随时都可能被改变,所以需要多关注一下。

合成

渲染层的合并,就是把页面中完成了绘制过程的部分合并成一层,然后显示在屏幕上。下面和合成相关的两点前面也有提到过。

使用transform/opacity实现动画效果

前面已经提到过 transform/opacity 的优势,应用了 transforms/opacity 属性的元素必须独占一个渲染层。为了对这个元素创建一个自有的渲染层,你必须提升该元素。

管理渲染层、避免过多数量的层

创建一个新的渲染层需要消耗额外的内存和管理资源。而在内存资源有限的设备上,由于过多的渲染层来带的开销而对页面渲染性能产生的影响,甚至远远超过了它在性能改善上带来的好处。由于每个渲染层的纹理都需要上传到 GPU 处理,因此我们还需要考虑 CPU 和 GPU 之间的带宽问题、以及有多大内存供 GPU 处理这些纹理的问题。

其他

  • 关注趋势,今天很多的性能瓶颈很可能在将来都不再是问题。如之前关注的一项技术 Web Animations ,是否能用 JS 达到原生动画效果。 Houdini ,你可以添加更多的 JS 代码到动画中而不用担心性能问题。

  • 利用工具 Chrome DevTools

    ,上面的规则只是优化的方向,善于利用工具分析。移动端利用 inspector 也是非常方便的,并且还可以对数据进行保存,对比分析等。几乎一切需要的分析工具,DevTools 都有。

  • マイクロ最適化を実行しないでください。短期間で得られるパフォーマンスの向上は非常に小さい場合があります。毎日の高速反復ビジネスでは、これを行う必要はありません。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。