[フロントエンド] ブラウザレンダリングの仕組みを理解して、ブラウザに優しいJavaScriptを書こう
こんにちは、@yoheiMuneです。
ブラウザのレンダリングの仕組みはHTML5 RocksやHow browsers workで詳しく解説されてきました。しかしそれらはとても詳細で、読破して理解するのは大変です。
今回のブログでは手軽にレンダリングの概要を理解できるように心がけました。またより詳しく学べるようなリンクも記載しました。
そしてブラウザのレンダリングの仕組みを理解した上で、どのようなJavaScriptを書くべきかについても記載しました。
以上がレンダリングの仕組みの概要です。 各項目においてそれぞれ細かな処理内容はありますが、ざっくりとした概要は上記の通りです。
画面の初期表示では上記の一連の処理が行われますが、JavaScriptによるDOM操作でもレンダリング処理の一部が実行されます。 JavaScriptからどのような処理を行うと、どのようなレンダリング処理が行われるのでしょうか。 次の章では具体的な例を用いて、その点を説明します。
ブラウザはそのような自体を避けるために、レンダリング処理の回数を減らすための最適化を行います。 最適化の一つの手法として「何もしない」または「まとめて行う」というものです。
ブラウザはJavaScriptからのスタイルの変更要求をキューにためて最後にまとめて処理を行うことで、レイアウトとペイントの発生回数を最小限にとどめるようにします。 例えば以下のような関数があるとします。
例えば以下のようにJavaScriptを書くと、ループの度にスタイルの計算が行われます。
このような事態を避ける方法は、要素位置の参照回数はできるだけ減らすことです。 例えば以下のように最初の1回のみ要素の位置を参照して、その後はキャッシュした情報を使うようにすることで、ブラウザの最適化を邪魔しないようになります。
本ブログではフロントエンド技術を中心に発信しています。ぜひ気になったら、RSSやTwitterをフォローしてみてください。最新の記事をお届けします☆
最後までご覧頂きましてありがとうございました。
ブラウザのレンダリングの仕組みはHTML5 RocksやHow browsers workで詳しく解説されてきました。しかしそれらはとても詳細で、読破して理解するのは大変です。
今回のブログでは手軽にレンダリングの概要を理解できるように心がけました。またより詳しく学べるようなリンクも記載しました。
そしてブラウザのレンダリングの仕組みを理解した上で、どのようなJavaScriptを書くべきかについても記載しました。
目次
ブラウザのレンダリングの仕組み
この章では、HTMLとCSSが読み込まれてから画面に表示されるまでの間に、ブラウザがどのような処理を行っているかを説明します。 ファイル読み込みから表示までの一連の流れは以下図の通りです。-
[1]
読み込んだHTMLを解析してDOMツリーを生成します。解析方法の詳細は解析-概要 | HTML5Rocksをご参照ください。 -
[2]
読み込んだCSSも解析してCSSの構造体を生成します。この構造体の呼び名はいくつかありますが、ここではCSSOM(CSS Object Model)と呼ぶこととします。CSSの解析の詳細はCSSの解析 | HTML5Rocksをご参照ください。 -
[3]
DOMツリーとCSSOMから画面表示に必要なレンダーツリー(Render Tree)を構築します。レンダーツリーにはDOMの構造と装飾の両方の情報が含まれます。ここでレンダーツリーとDOMツリーの違いは何かと思うかもしれません。それは、DOMツリーには全てのDOMが格納されている一方で、レンダーツリーには表示する要素のみが格納される(head
タグやdisplay:none
の要素は含まれない)ということです。この処理の詳細はレンダーツリーの構築 | HTML5 Rocksをご参照ください。 -
[4]
レイアウト(またはリフロー)ではレンダーツリーが持つ各DOM要素の位置を決定します。「レイアウト」という呼び方はWebkit系で「リフロー」という呼び方はGekko系から来る呼び方です。この処理の詳細はレイアウト | HTML5Rocksをご参照ください。 -
[5]
ペイントでは画面への描画処理を行います。この処理の結果ようやく画面に表示されます。この処理の詳細は描画 | HTML5Rocksをご参照ください。
以上がレンダリングの仕組みの概要です。 各項目においてそれぞれ細かな処理内容はありますが、ざっくりとした概要は上記の通りです。
画面の初期表示では上記の一連の処理が行われますが、JavaScriptによるDOM操作でもレンダリング処理の一部が実行されます。 JavaScriptからどのような処理を行うと、どのようなレンダリング処理が行われるのでしょうか。 次の章では具体的な例を用いて、その点を説明します。
どのような時にレンダリング処理が発生するのか
この章では、初期表示以外にどのような場面でレンダリング処理が発生するのかについて説明します。 この章を通して、どのような時にどんなレンダリング処理が発生するのかの勘所を掴んで頂けたら幸いです。JavaScriptで要素のスタイルを変更する
JavaScriptから要素のスタイルを変更すると、変更した内容に応じてレイアウトやペイントが発生します。 以下の例では、body
の各スタイルを変更した場合にどのようなレンダリングイベントが発生するかを示しています。// 余白を変更する document.body.style.padding = '30px'; // Layout と Paint が発生
// ボーダーを変更する document.body.style.border = '10px solid red'; // Layout と Paint が発生
// フォント色を変更する document.body.style.color = 'blue'; // paint のみ発生
// 背景色を変更する document.body.style.backgroundColor = '#fad'; // paint のみ発生上記の例の通り「レイアウトとペイントの両方」が発生する場合と「ペイントのみ」が発生する場合があります。 余白やボーダーを変更した場合には要素の位置調整が必要なためレイアウトイベントが発生し、その後画面に表示するためにペイントイベントも発生します。 フォント色などの色のみの変更で要素の位置を変える必要がない場合には、ペイントイベントのみ発生します。
JavaScriptからDOMを追加する
JavaScriptからDOMを追加することで、DOMツリー、レンダーツリー、レイアウト、ペイントの各イベントが発生します。// ul以下にliを追加する var ul = document.querySelector('ul'); var li = document.createElement('li'); li.textContent = 'JavaScriptで挿入したli要素'; ul.appendChild(li);
ユーザー操作
様々なユーザー操作でもレイアウトやペイントが発生します。以下にはそれらイベント発生するユーザーアクションの一例を示します。- スクロールする
- ウインドウのサイズを変更する
- :hover要素などにマウスオーバーしてスタイルが切り替わる
- など
ブラウザは頭が良い
さて一つ前の章では、様々な場面でレンダリング処理が発生することが分かりました。 レンダリング処理は得てして重たい処理です。何度も頻繁に発生すると画面がカクつくことにもなってしまいます。ブラウザはそのような自体を避けるために、レンダリング処理の回数を減らすための最適化を行います。 最適化の一つの手法として「何もしない」または「まとめて行う」というものです。
ブラウザはJavaScriptからのスタイルの変更要求をキューにためて最後にまとめて処理を行うことで、レイアウトとペイントの発生回数を最小限にとどめるようにします。 例えば以下のような関数があるとします。
function changePosition () { var icon = document.querySelector('.icon'); icon.style.top = '10px'; icon.style.top = '20px'; icon.style.top = '30px'; icon.style.left = '100px'; }この関数内では合計4回スタイルを変更していますが、レイアウトとペイントは最後に1回ずつしか発生しません。 このようにブラウザはレンダリングの回数を減らすように最適化を行います。
ブラウザのレンダリング最適化を壊さないJavaScript実装
しかしそのような最適化を壊すJavaScriptを書くことができます。 それはJavaScript内で要素の以下のようなスタイルを読み込む場合です。offsetTop/Left/Width/Height
scrollTop/Left/Width/Height
getComputedStyle
例えば以下のようにJavaScriptを書くと、ループの度にスタイルの計算が行われます。
var div = document.querySelector('div'); for (var i = 0; i < 100; i++) { // offsetLeftを参照するたびに、スタイルの再計算が強制的に行われる. var offsetLeft = div.offsetLeft; div.style.left = (offsetLeft + 1) + 'px'; }このような実装は、せっかくのブラウザの最適化を台無しにしてしまいます。
このような事態を避ける方法は、要素位置の参照回数はできるだけ減らすことです。 例えば以下のように最初の1回のみ要素の位置を参照して、その後はキャッシュした情報を使うようにすることで、ブラウザの最適化を邪魔しないようになります。
var div = document.querySelector('div'); var offsetLeft = div.offsetLeft; for (var i = 0; i < 100; i++) { offsetLeft += 1; div.style.left = offsetLeft + 'px'; }このようにレンダリングの仕組みを理解することで、より高速なJavaScriptを実装することができるようになります。
最後に
今回はブラウザのレンダリングの仕組みと、それを意識したJavaScriptの実装についてブログを書きました。 レンダリングの最適化の話は60FPSを意識した実装などもっと多くのことを書きたいところです。その内容についてはまた別の記事で言及できたらと思います。本ブログではフロントエンド技術を中心に発信しています。ぜひ気になったら、RSSやTwitterをフォローしてみてください。最新の記事をお届けします☆
最後までご覧頂きましてありがとうございました。