ホームページ >ウェブフロントエンド >jsチュートリアル >高性能JavaScriptリフローと再描画(2)_JavaScriptスキル

高性能JavaScriptリフローと再描画(2)_JavaScriptスキル

WBOY
WBOYオリジナル
2016-05-16 15:45:461507ブラウズ

前回の記事 ハイパフォーマンス JavaScript DOM プログラミング を復習してください。主に 2 つの最適化を提案しています。1 つは DOM アクセスを最小限に抑え、計算を ECMAScript 側に置くことです。最後に、2 つの新しい API querySelector() と querySelectorAll() が導入され、組み合わせて選択するときに大胆に使用できます。この記事では主に、DOM プログラミング、再配置、再描画 の中でおそらく最も時間のかかる部分 について説明します。

1. リフローとリドローとは
ブラウザは、ページ内のすべてのコンポーネント (HTML タグ、JavaScript、CSS、画像) をダウンロードした後、2 つの内部データ構造 (DOM ツリーとレンダリング ツリー) を解析して生成します。

DOM ツリーはページ構造を表し、レンダリング ツリーは DOM ノードがどのように表示されるかを表します。表示する必要がある DOM ツリー内の各ノードには、レンダリング ツリー内に対応するノードが少なくとも 1 つあります (表示値が none の非表示の DOM 要素には、レンダリング ツリー内に対応するノードがありません)。レンダリング ツリー内のノードは「フレーム」または「ボックス」と呼ばれ、CSS モデルの定義に準拠しており、ページ要素をパディング、マージン、境界線、および位置を備えたボックスとして認識します。 DOM とレンダー ツリーが構築されると、ブラウザはページ要素の表示 (描画) を開始します。

DOM の変更が要素の幾何学的プロパティ (幅または高さ) に影響を与える場合、ブラウザは要素の幾何学的プロパティを再計算する必要があり、他の要素の幾何学的プロパティと位置も影響を受けます。ブラウザはレンダー ツリーの影響を受けた部分を無効にし、レンダー ツリーを再構築します。このプロセスは再配置と呼ばれます。リフローが完了すると、ブラウザは影響を受けた部分を画面に再描画します。これは再描画と呼ばれるプロセスです。ブラウザのフロー レイアウトにより、レンダー ツリーの計算は通常 1 回実行するだけで済みます。テーブルとその内部要素を除いて、レンダリング ツリー内のノードの属性を決定するために複数の計算が必要になる場合があり、これには通常、同等の要素の 3 倍の時間がかかります。これが、レイアウトにテーブルの使用を避けるべき理由の 1 つです。

すべての DOM 変更が幾何学的プロパティに影響するわけではありません。たとえば、要素の背景色の変更は要素の幅と高さに影響しません。この場合、再描画のみが行われます。

2. 再配置と再描画の費用はいくらですか
リフローと再塗装の費用はどれくらいかかりますか?先ほどの橋を渡る例に戻りましょう。注意していれば、千倍の時間差は「橋を渡る」ことによって生じているわけではないことがわかります。「橋を渡る」たびに、実際には再配置と再描画が伴います。 . そして、エネルギー消費のほとんどはここにあります!

var times = 15000;

// code1 每次过桥+重排+重绘
console.time(1);
for(var i = 0; i < times; i++) {
 document.getElementById('myDiv1').innerHTML += 'a';
}
console.timeEnd(1);

// code2 只过桥
console.time(2);
var str = '';
for(var i = 0; i < times; i++) {
 var tmp = document.getElementById('myDiv2').innerHTML;
 str += 'a';
}
document.getElementById('myDiv2').innerHTML = str;
console.timeEnd(2);

// code3 
console.time(3);
var _str = '';
for(var i = 0; i < times; i++) {
 _str += 'a';
}
document.getElementById('myDiv3').innerHTML = _str;
console.timeEnd(3);


// 1: 2874.619ms
// 2: 11.154ms
// 3: 1.282ms

データは嘘をつきません。DOM に複数回アクセスしても、リフローと再描画にかかる時間は言うまでもありません。

3. 再配置はいつ行われます
明らかに、すべての再配置は必然的に再描画につながります。では、どのような状況で再配置が発生するのでしょうか?

1. 表示される DOM 要素を追加または削除します
2. 要素の位置を変更します
3. 要素のサイズ変更
4. 要素の内容が変更される (例: テキストが異なるサイズの別の画像に置き換えられる)
5. ページレンダリングの初期化 (これは回避できません)
6. ブラウザウィンドウのサイズ変更
これらはすべて明らかです。ブラウザ ウィンドウのサイズを頻繁に変更すると、UI の応答が遅くなるという経験をしたことがあるかもしれません (IE の一部のバージョンが直接ハングすることもあります)。何度も再配置したり再描画したりすることが原因です。

4. レンダー ツリー変更のキューイングと更新
次のコードを考えてみましょう:

var ele = document.getElementById('myDiv');
ele.style.borderLeft = '1px';
ele.style.borderRight = '2px';
ele.style.padding = '5px';

最初に考えたのは、要素のスタイルが 3 回変更されており、変更のたびに再配置と再描画が発生するため、合計 3 回の再配置と再描画のプロセスがありますが、ブラウザはそれほど愚かではなく、3 回変更します。 「保存」(ほとんどのブラウザは、変更をキューに入れてバッチで実行することで並べ替えプロセスを最適化します)は、一度で完了します。ただし、場合によっては (多くの場合無意識のうちに) キューのフラッシュを強制し、スケジュールされたタスクの即時実行が必要になる場合があります。レイアウト情報を取得する操作により、次のようなキューが更新されます。

1.offsetTop、offsetLeft、offsetWidth、offsetHeight
2.scrollTop、scrollLeft、scrollWidth、scrollHeight
3.clientTop、clientLeft、clientWidth、clientHeight
4.getComputedStyle() (IE の currentStyle)
上記のコードを少し変更します:

var ele = document.getElementById('myDiv');
ele.style.borderLeft = '1px';
ele.style.borderRight = '2px';

// here use offsetHeight
// ...
ele.style.padding = '5px';

因为offsetHeight属性需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的“待处理变化”并触发重排以返回正确的值(即使队列中改变的样式属性和想要获取的属性值并没有什么关系),所以上面的代码,前两次的操作会缓存在渲染队列中待处理,但是一旦offsetHeight属性被请求了,队列就会立即执行,所以总共有两次重排与重绘。所以尽量不要在布局信息改变时做查询。

5、最小化重排和重绘
我们还是看上面的这段代码:

var ele = document.getElementById('myDiv');
ele.style.borderLeft = '1px';
ele.style.borderRight = '2px';
ele.style.padding = '5px';

三个样式属性被改变,每一个都会影响元素的几何结构,虽然大部分现代浏览器都做了优化,只会引起一次重排,但是像上文一样,如果一个及时的属性被请求,那么就会强制刷新队列,而且这段代码四次访问DOM,一个很显然的优化策略就是把它们的操作合成一次,这样只会修改DOM一次:

var ele = document.getElementById('myDiv');

// 1. 重写style
ele.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';

// 2. add style
ele.style.cssText += 'border-;eft: 1px;'

// 3. use class
ele.className = 'active';

6、fragment元素的应用
看如下代码,考虑一个问题:

<ul id='fruit'>
 <li> apple </li>
 <li> orange </li>
</ul>

如果代码中要添加内容为peach、watermelon两个选项,你会怎么做?

var lis = document.getElementById('fruit');
var li = document.createElement('li');
li.innerHTML = 'apple';
lis.appendChild(li);

var li = document.createElement('li');
li.innerHTML = 'watermelon';
lis.appendChild(li);

很容易想到如上代码,但是很显然,重排了两次,怎么破?前面我们说了,隐藏的元素不在渲染树中,太棒了,我们可以先把id为fruit的ul元素隐藏(display=none),然后添加li元素,最后再显示,但是实际操作中可能会出现闪动,原因这也很容易理解。这时,fragment元素就有了用武之地了。

var fragment = document.createDocumentFragment();

var li = document.createElement('li');
li.innerHTML = 'apple';
fragment.appendChild(li);

var li = document.createElement('li');
li.innerHTML = 'watermelon';
fragment.appendChild(li);

document.getElementById('fruit').appendChild(fragment);

文档片段是个轻量级的document对象,它的设计初衷就是为了完成这类任务——更新和移动节点。文档片段的一个便利的语法特性是当你附加一个片断到节点时,实际上被添加的是该片断的子节点,而不是片断本身。只触发了一次重排,而且只访问了一次实时的DOM。

7、让元素脱离动画流
用展开/折叠的方式来显示和隐藏部分页面是一种常见的交互模式。它通常包括展开区域的几何动画,并将页面其他部分推向下方。

一般来说,重排只影响渲染树中的一小部分,但也可能影响很大的部分,甚至整个渲染树。浏览器所需要重排的次数越少,应用程序的响应速度就越快。因此当页面顶部的一个动画推移页面整个余下的部分时,会导致一次代价昂贵的大规模重排,让用户感到页面一顿一顿的。渲染树中需要重新计算的节点越多,情况就会越糟。

使用以下步骤可以避免页面中的大部分重排:

使用绝对位置定位页面上的动画元素,将其脱离文档流
让元素动起来。当它扩大时,会临时覆盖部分页面。但这只是页面一个小区域的重绘过程,不会产生重排并重绘页面的大部分内容。
当动画结束时恢复定位,从而只会下移一次文档的其他元素
总结
重排和重绘是DOM编程中耗能的主要原因之一,平时涉及DOM编程时可以参考以下几点:

尽量不要在布局信息改变时做查询(会导致渲染队列强制刷新)
同一个DOM的多个属性改变可以写在一起(减少DOM访问,同时把强制渲染队列刷新的风险降为0)
如果要批量添加DOM,可以先让元素脱离文档流,操作完后再带入文档流,这样只会触发一次重排(fragment元素的应用)
将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。

以上就是高性能JavaScript 重排与重绘的全部介绍内容,大家可以结合上一篇高性能JavaScript DOM编程(1)一起学习,

希望这两篇文章可以帮到大家,解决大家这方面的疑惑。

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