HTML5Experts.jp

Webアニメーションを高速化するために知っておくべき10のこと(後編)

前編から引き続き、後編でも最適化のために知っておきたいレンダリングプロセス、計測方法、そして最適化を妨げるよくあるアクシデントとその回避方法について紹介していきます。

アニメーションを高速化するために知っておきたいレンダリングプロセス

ブラウザがどのようにウェブサイトを表示しているのかを知ることは、アニメーションだけに限らず、Webのパフォーマンス全体の高速化を行うために大切なステップです。 イスラエルの開発者であるTali Garsiel氏が公開した『How Browsers Work』は、HTML5 Rocksに転載され、複数の日本語訳も提供されている、ブラウザの内部動作を学ぶために読んでおきたいリソースの1つです。 そのリソースを参考に、レンダリングエンジンのメインフローについて見ていきましょう。

ブラウザはまずHTMLドキュメントを解析し、HTMLタグをコンテント・ツリーと呼ばれるDOMノードに変換します。そして外部CSSやスタイル要素に含まれるスタイルに関するデータを解析し、レンダーツリーを作成します。
このレンダーツリーが色や大きさのような情報を持つ矩形を持っています。

ここから先が特にアニメーションのパフォーマンスに関わりの深いフローになりますが、レンダーツリーが作成された後、レイアウト(リフロー)と呼ばれるプロセスに入り、そしてペイントがそれに続きます。

ここでは非常に大まかな流れしか解説しませんが、Paul Irish氏が言うとおり、ブラウザがHTMLやCSSを画面に表示するまでのフローを理解することは、開発におけるベストプラクティスの背後にある根拠を理解する手助けとなります。少々慣れない用語が多いかもしれませんが、ぜひ先ほど紹介した記事を一読ください。

アニメーション・ボトルネック: レイアウト(リフロー)

Google ChromeやSafariなどのWebKit系ブラウザではレイアウト、FirefoxなどのGecko系ブラウザではリフローと呼ばれるプロセスは、パフォーマンス向上において大きな障害となるプロセスの1つです。
Satoshi Ueyama氏が2007年に『出張 Shibuya.js 24』にて発表した際に使用した以下の動画で見ることができます。様々な大きさの矩形が徐々に配置されていくプロセスがレイアウト/リフローです。

Gecko Reflow Visualization

このレイアウトのプロセスを、どのように最小限に抑えることができるかが、アニメーションの高速化において大切なポイントになります。 レイアウトを引き起こす原因について、詳しくはTony Gentilcore氏による「How (not) to trigger a layout in WebKit」を参考にしてください。
記事のタイトルにもある通り、あくまでWebKit系ブラウザのみに限定されていますし、2011年の記事でもありますので、必ずレイアウトの発生を確認できるツールを利用して確認することをおすすめします。
(執筆時点ではGoogle Chrome、Safariの開発者ツール、そして紹介記事から得た情報でしかありませんが、IE11の開発者ツールでもレイアウトの発生を確認できます)

アニメーション・ボトルネック:ペイント

やや乱暴ではありますが、ブラウザで何かを表示するということは、HTMLやCSSから得た情報を画像化して表示しているのと大きな差はないと言えます。 

CSS3がプロダクションレベルで利用されはじめた今、角丸やシャドウなどを画像を利用せずに表現することが当たり前になってきているかと思います。読み込む画像が少なくなれば、その画像を制作するコストも減り、画像の読み込みにかかるコストも減りますが、その代わりにブラウザがそのコストを背負ってくれているわけです(もちろん1対1の割合ではありませんが)。
この後の段でアクシデントとして紹介しますが、すべてのスタイルが同じコストでペイントを行うわけではなく、また組み合わせによってコストが甚大になるケースもあるので注意が必要になります。

ブラウザは、表示領域をいくつかのタイルとしてレンダリングを行います。それぞれの個別のタイルごとにレンダリングをし、その結果をキャッシュしていきます。
そのため、大きな領域でペイントを行うことを避ける必要があります。レイアウトと同様に、ペイントを行う範囲を最低限にしておくことがポイントになります。

アニメーションのパフォーマンスの計測、デバッグツール/ワークフロー

パフォーマンス最適化において、最も大事なことは計測することです。私自身もよく陥る失敗ですが、どこかで読んだから、以前はうまく回避できた問題だからと盲目的にそれらの情報を頼りにするのではなく、計測する方法をきちんと学び、計測を日々続けることがベストプラクティスです。

開発者ツールについての詳しい記事は今後本サイトで紹介される予定ですので、ここでは私が実際にアニメーションのパフォーマンスについて計測するのに利用しているツールとワークフローを、ほんの一部ですが紹介していきます。

パフォーマンス計測を行う上で欠かすことができないのが、Google Chrome Canaryの開発者ツールです。Chromeではなく、日々更新を続けていくCanaryを利用する理由は、計測ツールとしての機能の多さと深さがChromeとは異なるからです。

アニメーションの最適化という文脈においてタイムラインパネルは、エディタとブラウザの描画エリアと同じくらい見る画面です。

まずは上図の1、Framesタブに切り替えて、上図2のRecordボタンをクリックし、計測を行いたいアニメーションを実行します。

すると、上図のような棒グラフが記録されていきます。その下にある表は棒グラフをより詳細にしたものです。
棒グラフは、背が高ければ高いほど処理に時間がかかっているということになります。計測対象にもよりますが、グラフ上にある2本の線は30FPSと60FPSを示しています。60FPSを目指すのであれば、それ以上の高さになっている棒グラフの周りをドラッグすることで、下にある表にその選択したタイミング内の詳細情報を表示させることができるようになります。

表にある横グラフは、マウスオーバーさせることでその特定の処理についての詳細を見ることができます。
レイアウトやペイントがどの範囲で行われているか、そしてそれにかかっている時間がどのくらいか、などの情報を確認することが可能です。
これらの情報と最前にも紹介したレンダリングの仕組みの情報を踏まえて、トライアル&エラーを繰り返し、最適化を図っていくのが大まかなワークフローとなります。

アニメーションを遅くするよくあるアクシデント

最後にアニメーションのパフォーマンス最適化を妨げる、よくある3つのアクシデントを紹介します。

1つ目は本連載の前編でも少し触れたGPUにまつわるアクシデントです。
GPUを使ってRenderLayerを作成し、そのレイヤー上でアニメーションを行うことそのものはベストプラクティスと言えます。しかしCPUからGPUへのデータ転送にはコストがかかり、特にモバイルデバイスなどCPUもGPUも非力である場面で問題になります。

この問題については、Phamtom.jsの作者としてよく知られるAriya Hidayat氏によるデモが非常にわかりやすいです。デモそのものはこちらのcodepenで実際に動作しているので、参考にしてください。

Ariya Hidayat氏によるGPUへのデータアップロードデモ

それぞれのボックスの右上に数字があり、カウントされているのが確認できたでしょうか? このカウントがCPUからGPUへのデータの転送数を示しています。このデータ転送が少ないほうがパフォーマンスはよくなります。
ですが、実際にデータ転送を行わないプロパティは非常に少なく、Ariya Hidayat氏によれば、opacity、transform、filterの3つのみと言われています。
アニメーションの表現としてデータ転送が回避できない場面が多いのも事実ですが、同時にデータ転送には相応のコストがかかることを知っておくことも大切です。

GPU関連でもう1つの落とし穴があります。GoogleのJake Archibald氏によるデモをご覧ください。

オレンジ色の矩形が、GPUによって処理されるComposited Layerを示しています。実際にComposited Layerを生成したかったのは左上の緑のボックスだけなのにも関わらず、どういうわけか見出しにもComposited Layerが生成されてしまいます。デスクトップ上であれば、このComposited Layerの生成にかかるコストは気にするほどのことでもありませんが、モバイルデバイスにおいては大きなコストになってしまいます。
この問題を回避するのにはシンプルな解決があります。

先ほどのデモ内においてはアニメーションする要素そのものに、z-index: 1を付与することで回避できます。
このアクシデントは、カルーセルのようなUIパターンによく見られます。自作する際も、すでにあるライブラリを利用する際も、思っていない場面でComposited Layerが生成されていないか確認し、z-indexを使って回避できるか試してみてください。

Ariya Hidayat氏によるW3Confでのプレゼンテーション『Fluid User Interface with Hardware Acceleration』はハードウェア・アクセラレーションを使った最適化を行うのにあたり、見ておくべきリソースの1つです。またThe Chromium ProjectsにてドキュメントされているGPU Accelerated Compositing in Chromeも参考になりますので、ぜひ一読してください。

ここで紹介する最後のよくあるアクシデントが、CSSプロパティの組み合わせによるペイントコストの増大です。このアクシデントはモバイルデバイスでよく見かけるものです。例えば、角丸とシャドウを組み合わせたボックス、非常によくあるスタイルにも関わらず、これらのスタイルがあると、とたんにスクロールが引っかかるように感じてしまうことがあります。この「引っかかる感じ」の原因の多くがペイントです。

どのプロパティの組み合わせがどれくらいのペイントコストとなるかを計測するためのツールは存在はしますが、ここでは紹介しきれませんので割愛します(チャレンジしてみたい方はこちらのドキュメントを参考にしてください。また取得方法は異なりますが、Colt McAnlis氏によるHTML5 Rocksの記事『CSS Paint Times and Page Render Weight』も合わせて参考にしてください。)。しかし前段で紹介したタイムラインパネルでもペイントコストを確認できますので、トライ&エラーを繰り返すことで、最適化を進められるでしょう。

前後編にわたってWebアニメーションの最適化について紹介してきました。
いずれブラウザや端末そのものも賢くなり、これらの知識やテクニックも早晩時代遅れになっていくだろうと思います。しかし、我々はその過渡期にいる最中です。「使いやすさ」を向上する上で欠かすことができない最適化は多くの苦難があっても報われるものです。