ホームページ > 記事 > ウェブフロントエンド > ページの再描画とリフローの最適化方法
ディスカッションページが再描画されリフローされる前。ページのレンダリング プロセス、CSS と組み合わせてページがどのようにブラウザーに表示されるかをある程度理解する必要があります。次のフローチャートは、ブラウザーのページ レンダリングの処理フローを示しています。ブラウザが異なると若干異なる場合があります。しかし、基本的には似ています。
ブラウザは、取得した HTML コードを DOM ツリーに解析します。HTML の各タグは DOM ツリー内のノードであり、ルート ノードは一般的に使用されるドキュメント オブジェクトです。 DOM ツリーには、display:none 非表示、JS を使用して動的に追加された要素などを含むすべての HTML タグが含まれます。
ブラウザーは、すべてのスタイル (ユーザー定義の CSS およびユーザー エージェント) をスタイル構造に解析します。たとえば、ブラウザーが認識できないスタイルは、-moz で始まるスタイルを削除します。 _ で始まる FF スタイルは削除されます。
3. DOM ツリーとスタイル構造を組み合わせてレンダー ツリーを構築します。レンダー ツリーは DOM ツリーに似ていますが、その違いは非常に大きくなります。レンダー ツリーには独自のスタイルがあり、レンダー ツリーには非表示ノード (display:none ノードやヘッド ノードなど) が含まれません。これらのノードはレンダリングに使用されず、レンダリングに影響を与えないため、これらのノードはレンダリングに含まれません。レンダーツリー。 Visibility:hidden はレイアウトに影響を与え、スペースを占有するため、visibility:hidden で非表示にされた要素は引き続きレンダー ツリーに含まれることに注意してください。 CSS2 標準によれば、レンダー ツリー内の各ノードはボックス (ボックスの寸法) と呼ばれ、ページ要素はパディング、マージン、境界線、および位置を備えたボックスであると理解されます。
レンダー ツリーが構築されると、ブラウザはレンダー ツリーに基づいてページを描画できるようになります。
リフローと再描画
要素のサイズ、レイアウト、非表示などの変更により、レンダー ツリーの一部 (またはすべて) を再構築する必要がある場合。これをリフローといいます。すべてのページを少なくとも 1 回リフローする必要があります。これは、ページが初めて読み込まれるときです。リフロー中、ブラウザはレンダリング ツリーの影響を受けた部分を無効にし、リフローが完了した後、影響を受けた部分を画面に再描画します。このプロセスは再描画と呼ばれます。
レンダーツリー内の一部の要素で属性を更新する必要がある場合、これらの属性は要素の外観とスタイルにのみ影響し、背景色などのレイアウトには影響しません。それを再描画といいます。
注: リフローは確実に再描画を引き起こしますが、再描画が必ずしもリフローを引き起こすとは限りません。
リフローが発生する場合:
ページ レイアウトと幾何学的プロパティが変更された場合は、リフローが必要です。ブラウザのリフローは次の状況で発生します:
1. 表示される DOM 要素の追加または削除
3. 要素のサイズの変更 - 余白、パディング、幅、高さ
4. . コンテンツの変更 - テキストの変更や画像サイズの変更による、計算された値の幅と高さの変更
6. サイズ変更イベントが発生したとき。次のコードがリフローと再描画にどのような影響を与えるかを見てみましょう:
var s = document.body.style; s.padding = "2px"; // 回流+重绘 s.border = "1px solid red"; // 再一次 回流+重绘 s.color = "blue"; // 再一次重绘 s.backgroundColor = "#ccc"; // 再一次 重绘 s.fontSize = "14px"; // 再一次 回流+重绘// 添加node,再一次 回流+重绘 document.body.appendChild(document.createTextNode('abc!'));
これについて言えば、リフローのコストは再構築する必要があるレンダー ツリーのノードの数に関係していることは誰もが知っています。ボディを直接操作します。たとえば、ボディの前に要素を挿入すると、レンダー ツリー全体がリフローされ、当然コストが高くなりますが、ボディの後に要素を挿入した場合、要素には影響しません。前の要素のリフロー
スマート ブラウザ
前のコード例から、数行の単純な JS コードで約 6 回のリフローと再描画が発生したことがわかります。また、リフローのコストが小さくないこともわかっています。すべての JS 操作をリフローして再描画する必要がある場合、ブラウザーはそれに耐えられない可能性があります。したがって、多くのブラウザはこれらの操作を最適化するため、キューを維持し、キュー内の操作が特定の数または一定の時間間隔に達すると、リフローと再描画を引き起こすすべての操作をこのキューに入れます。フラッシュされ、バッチが処理されます。これにより、複数のリフローと再描画が 1 つのリフローと再描画に変わります。
ブラウザの最適化はありますが、作成するコードの一部がブラウザに事前にキューをフラッシュさせる場合があるため、ブラウザの最適化が効果的でない場合があります。ブラウザから何らかのスタイル情報をリクエストすると、ブラウザは次のようなキューをフラッシュします:
offsetTop, offsetLeft, offsetWidth, offsetHeight scrollTop/Left/Width/Height clientTop/Left/Width/Height width,height 请求了getComputedStyle(), 或者 IE的 currentStyle
上記の属性の一部をリクエストすると、最も正確な値を提供するために、ブラウザはキューをフラッシュします。これらの値に影響を与える操作がキュー内にある可能性があるため、フラッシュする必要があります。要素に対して取得したレイアウトおよびスタイル情報が、最近発生または変更されたレイアウト情報と何の関係もない場合でも、ブラウザーはレンダリング キューを強制的に更新します。
减少回流、重绘 其实就是需要减少对 render tree 的操作(合并多次多DOM和样式的修改),并减少对一些style信息的请求,尽量利用好浏览器的优化策略。具体方法有:
直接改变className,如果动态改变样式,则使用cssText(考虑没有优化的浏览器)
// 不好的写法var left = 1;var top = 1; el.style.left = left + "px"; el.style.top = top + "px";// 比较好的写法el.className += " className1";// 比较好的写法el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
让要操作的元素进行”离线处理”,处理完后一起更新
a) 使用 DocumentFragment 进行缓存操作,引发一次回流和重绘;
b) 使用 display:none 技术,只引发两次回流和重绘;
c) 使用 cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘;
3.不要经常访问会引起浏览器 flush 队列的属性,如果你确实要访问,利用缓存
// 别这样写,大哥 for(循环) { el.style.left = el.offsetLeft + 5 + "px";el.style.top = el.offsetTop + 5 + "px";} // 这样写好点 var left = el.offsetLeft, top = el.offsetTop, s = el.style; for (循环) { left += 10; top += 10; s.left = left + "px"; s.top = top + "px"; }
让元素脱离动画流,减少回流的Render Tree的规模
$("#block1").animate({left:50}); $("#block2").animate({marginLeft:50});
实例测试
最后用2个工具对上面的理论进行一些测试,分别是:dynaTrace(测试ie),Speed Tracer(测试Chrome)。
第一个测试代码不改变元素的规则,大小,位置。只改变颜色,所以不存在回流,仅测试重绘,代码如下:
<body> <script type="text/javascript"> var s = document.body.style; var computed; if (document.body.currentStyle) { computed = document.body.currentStyle; } else { computed = document.defaultView.getComputedStyle(document.body, ''); } function testOneByOne(){ s.color = 'red';; tmp = computed.backgroundColor; s.color = 'white'; tmp = computed.backgroundImage; s.color = 'green'; tmp = computed.backgroundAttachment; } function testAll() { s.color = 'yellow'; s.color = 'pink'; s.color = 'blue'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment; } </script> color test <br /> <button onclick="testOneByOne()">Test One by One</button> <button onclick="testAll()">Test All</button></body>
testOneByOne 函数改变3次color,其中每次改变后调用getComputedStyle,读取属性值(按我们上面的讨论,这里会引起队列的 flush),testAll 同样是改变3次color,但是每次改变后并不马上调用getComputedStyle。
我们先点击Test One by One按钮,然后点击 Test All,用dynaTrace监控如下:
上图可以看到我们执行了2次button的click事件,每次click后都跟一次rendering,2次click函数执行的时间都差不多, 0.25ms,0.26ms,但其后的rendering时间就相差一倍多。(其实很多时候,前端的性能瓶颈并不在于JS的执行,而是在于页面的呈现,这种情况在富客户端中更为突出)。我们再看图的下面部分,这是第一次rendering的详细信息,可以看到里面有2行是 Scheduleing layout task,这个就是我们前面讨论过的浏览器优化过的队列,可以看出我们引发2次的flush。
再看第二次rendering的详细信息,可以看出并没有Scheduleing layout task,所以这次rendering的时间也比较短。
测试代码2:这个测试跟第一次测试的代码很类似,但加上了对layout的改变,为的是测试回流。
<script type="text/javascript"> var s = document.body.style; var computed; if (document.body.currentStyle) { computed = document.body.currentStyle; } else { computed = document.defaultView.getComputedStyle(document.body, ''); } function testOneByOne(){ s.color = 'red'; s.padding = '1px'; tmp = computed.backgroundColor; s.color = 'white'; s.padding = '2px'; tmp = computed.backgroundImage; s.color = 'green'; s.padding = '3px'; tmp = computed.backgroundAttachment; } function testAll() { s.color = 'yellow'; s.padding = '4px'; s.color = 'pink'; s.padding = '5px'; s.color = 'blue'; s.padding = '6px'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment; } </script> color test <br /> <button onclick="testOneByOne()">Test One by One</button> <button onclick="testAll()">Test All</button>
这图可以看出,有了回流后,rendering的时间相比之前的只重绘,时间翻了3倍了,可见回流的高成本性啊。
大家看到时候注意明细处相比之前的多了个 Calcalating flow layout。
最后再使用Speed Tracer测试一下,其实结果是一样的,只是让大家了解下2个测试工具:
测试1:
图上第一次点击执行2ms (其中有50% 用于style Recalculation), 第二次1ms,而且第一次click后面也跟了2次style Recalculation,而第二次点击却没有style Recalculation。
但是这次测试发现paint重绘的时间竟然是一样的,都是3ms,这可能就是chrome比IE强的地方吧。
测试2:
从图中竟然发现第二次的测试结果在时间上跟第一次的完全一样,这可能是因为操作太少,而chrome又比较强大,所以没能测试明显结果出来。
但注意图中多了1个紫色部分,就是layout的部分。也就是我们说的回流。
以上がページの再描画とリフローの最適化方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。