すごい記事が1日目2日目で来ている中で恐縮ではありますが、フロントエンドJavaScriptでパフォーマンス点から気にしたほうがいい部分について書いてみることにします。
DOM律速になるケースもある
よく「JavaScriptが遅い」ということも多いのですが、具体的にはどのあたりが遅くなってくるのでしょうか.
- 純粋にJavaScriptのスクリプト処理が遅い…最近はブラウザ自体も高速化しては来ましたが、それを追いかけるようにJavaScriptの巨大化も進んでいます。ただし、純粋にプログラム言語的な処理なら、Web Workerに振ることで並列化が可能です。
- Ajaxや画像読み込みなどの通信が遅い…サーバ側で高速化する手もありますが、状況によってはJavaScriptで先読みを始めておいて、体感時間を短くすることも可能かもしれません。
- DOM操作・表示が遅い…これについて今回考えてみます。
最大の特徴として、DOM関連の制御はシングルスレッドとなることがあります。Web WorkersやAjaxは、いくつも一気に起動して並行に進めることができますが、DOM操作はそうも行きません。個々の操作について、「不要なものを減らす」「速度に意識して行う」以外に、高速化の手段はないということです。
ReflowとRepaint
ブラウザがHTMLを表示するまでの流れは、大きく2段階にわかれています。
- 各HTML要素を解釈して、それぞれが占めるスペースを算出する
- 算出したスペースのなかに、内容を描画していく
Firefoxでは、前者をReflow、後者をRepaintといいます。Chromeでは前者を「Layout」と呼んでいたりもするのですが、この記事ではReflowで通します。
Reflowが起きるということは、つまり表示位置が変わることなので、連動してRepaintも起きるようになります。よく、「<img>
にwidthやheightを指定する」という高速化手法が挙げられることがありますが、これも、画像の寸法をあらかじめ固定することで、画像読み込み完了時にReflowが起きるのを予防するという効果があります。
Reflowが起きる時
Reflowは「表示すべきものの配置が変わる」ことをきっかけとして発生しえます。つまり、
- 要素の追加・削除
- すでに存在する要素の、寸法や余白が変わり得るスタイル変更
- ユーザーによるスクロールやブラウザのサイズ変更
などです。ただし、毎回Reflowしなくても処理はできるので、必要な時だけに限定しています。実際にReflowが起きるタイミングとしては、
- フレーム更新の直前
- JavaScriptから、寸法に関係するスタイル関係の値にアクセスする(参照を含めて)
などです。
#Reflowを減らすには
ということで、Reflowを減らす手段として、大きく2つに分ければ「Reflowになりうることを減らす」のと「Reflowが起きるきっかけを減らす」ということがあります。
まず、「Reflowになりうることを減らす」方法としては、
- 細かい単位でスタイルを変更せず、できればクラス一発で切り替える
- DOMに要素を追加する場合も、
documentFragment
などを使って、一気に追加する - DOMへの追加前にスタイルを整えて、それからDOMに追加する
などがあります。もう一方の、「Reflowが起きるきっかけを減らす」方法ですが、もちろんJavaScriptからフレームレートの調整はできませんので、「できるだけ寸法プロパティへの参照を避ける」ということが中心になります。
requestAnimationFrame
IE 10以降、Android 4.4以降などを含む多くのブラウザで使える関数として、requestAnimationFrame()
というものがあります。これはsetTimeout
と似たような感じで処理を遅らせる関数ですが、実行タイミングがフレーム再描画の直前、ということになります。そういうことで、ここでReflowが起きてもほとんどペナルティにならないので、名前の通りのアニメーションなど重いDOM処理をするには最適な場所となっています。