リフローと再描画
https://juejin.im/post/6844903734951018504#comment
ブラウザのレンダリングプロセス
- HTMLの解析、DOMツリーの生成、CSSの解析、CSSDOMツリーの生成
- DOMツリーとCSSDOMツリーを組み合わせてRender Treeを生成する
- レイアウト(リフロー):生成されたレンダリングツリーに従って、ノードの幾何学的情報(位置、サイズ)を取得するためにリフロー(レイアウト)
- ペイント:レンダリングツリーとリフローから取得した幾何学的情報に従って、ノードの絶対ピクセルを取得します
- ディスプレイ:ピクセルをGPUに送信してページに表示します
Render Treeを生成する
ブラウザの主な作業:
- DOMツリーのルートノードから表示されている各ノードのトラバースを開始します
- 表示されている各ノードから、CSSDOMツリーで対応するルールを見つけて適用します
- 表示されている各ノードとそれに対応するスタイルに従って、レンダリングツリーが結合されて生成されます。
注:最初のステップでは、可視ノードをトラバースする必要があります。非可視ノードには次のものが含まれます。
- スクリプト、メタ、リンクなど、ノードをレンダリングしない一部のノード
- display:noneなど、CSSによって非表示にされているノードがあります。(可視性と不透明度によって非表示にされたノードは引き続きレンダーツリーに表示されますが、表示のあるノードのみ:レンダーツリーには何も表示されません)
逆流(再配置)
レンダリングツリーを構築することで、表示されるDOMノードとそれに対応するスタイルが組み合わされ、デバイスのビューポート(ビューポート)での正確な位置とサイズを計算する必要があります。この計算段階はリフローです。
Webサイト上の各オブジェクトの正確なサイズと位置を見つけるために、ブラウザはレンダリングツリーのルートノードから移動します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div style="width:50%">
<div style="width:50%">hello world!</div>
</div>
</body>
</html>
最初のdivはノードの表示サイズをビューポートの幅の50%に設定し、2番目のdivはそのサイズを親ノードの50%に設定します。リフロー段階では、ビューポートの特定の幅に従って実際のピクセル値に変換する必要があります。
塗り直し
レンダリングツリーとリフローステージを構築し、どのノードが表示されているか、および表示されているノードのスタイルと特定の幾何学的情報(位置、サイズ)を把握している場合、レンダリングツリーの各ノードを画面上の実際のピクセルに変換できます。この段階はノードの再描画と呼ばれます。
いつリフロー再描画が発生しますか
リフローステージは主にノードの位置と幾何情報を計算するため、ページレイアウトと幾何情報が変更されると、リフローが必要になります。例えば:
- 表示されるDOM要素を追加または削除する
- 要素の位置が変わります
- 要素のサイズが変化します(内側と外側のマージン、内側の境界、境界のサイズ、幅と高さなど)
- コンテンツの変更(たとえば、テキストの変更や画像が別のサイズの別の画像に置き換えられた)
- ページが最初にレンダリングされたとき
- ブラウザウィンドウサイズの変更
リフローは間違いなくリドローをトリガーし、リドローは必ずしもリフローしません。
変更の範囲と範囲に応じて、Render Treeの大きな部分または小さな部分を再計算する必要があります。たとえば、スクロールバーが表示されたり、ルートノードが変更されたりした場合など、一部の変更によってページ全体の並べ替えがトリガーされます。
ブラウザ最適化メカニズム
再配置のたびに追加の計算消費が発生するため、ほとんどのブラウザーは、キューに入れられた変更とバッチ実行を通じて再配置プロセスを最適化します。ブラウザは変更操作をキューに入れ、時間が経過するか操作がしきい値に達するまでキューを空にします。ただし、レイアウト情報を取得する操作を行うと、プロパティへのアクセスや次のメソッドの使用など、キューが強制的に更新されます。
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTOP、clientLeft、clientWidth、clientHeight
- getComputedStyle()
- getBoundingClientRect()
上記の属性は最新のレイアウト情報にアクセスする必要があるため、ブラウザーはキューをクリアし、リフローの再描画をトリガーして正しい値を返す必要があります。したがって、スタイルを変更するときは、上記の属性の使用を避け、使用する場合は値をキャッシュすることをお勧めします。
リフローと再描画を削減
再描画と再配置を最小限に抑える
出現回数を減らし、DOMとスタイルへの複数の変更をマージして、一度にすべてを処理します
const el = document.getElementById('test')
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
この例では、3つのスタイル属性が変更されており、それぞれが要素の幾何学的構造に影響を与え、リフローを引き起こします。最近のほとんどのブラウザーはそれを最適化しているため、リフローは1回だけトリガーされます。しかし、それが古いブラウザにある場合は、3回のリフローが発生します。
const el = document.getElementById('test')
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
DOMの一括変更
DOMに一連の変更が必要な場合、次の手順を使用してリフローの再描画の数を減らすことができます。
- ドキュメントフローから要素を取り出す
- 複数の変更を加える
- 要素をドキュメントに戻す
最初と3番目のステップではリフローが発生する可能性がありますが、最初のステップの後は、DOMがRender Treeに存在しないため、DOMへのすべての変更によってリフローが発生することはありません。
ドキュメントフローからDOMを取得します。
- 要素の非表示、変更の適用、再表示
- DocumentFragmentを使用して現在のDOMの外にサブツリーを作成し、それをドキュメントにコピーする
- 元の要素をドキュメント外の別のノードにコピーし、ノードを変更した後、元の要素を置き換えます
同期レイアウトイベントのトリガーを回避する
要素の一部の属性にアクセスすると、ブラウザがキューを強制的に空にして、強制同期レイアウトを実行します。
例:pタグ配列の幅を別の要素の幅に割り当てたい
function initP() {
for(let i=0;i< paragraphs.length;i++) {
paragraphs[i].style.width = box.offsetWidth + 'px'
}
}
上記のコードは、ボックスが実行されるたびにボックスのoffsetWidthプロパティを読み取り、それを使用してpタグのwidthプロパティを更新します。その結果、ブラウザーは、このループのスタイル読み取り操作に応答するために、ループするたびに最後のループのスタイル更新操作を有効にする必要があります。サイクルごとに、ブラウザは強制的にキューを更新します。最適化は次のとおりです。
const width = box.offsetWidth
function initP() {
for(let i=0;i< paragraphs.length;i++) {
paragraphs[i].style.width = width + 'px'
}
}
複雑なアニメーション効果の場合は、絶対配置を使用してドキュメントフローから外します
css3ハードウェアアクセラレーション(GPUアクセラレーション)