IMPORTANT

この記事は、TechFeed Experts Night 14でのLT発表をベースに文章を再編成したものです。

ブラウザのレンダリングの大まかな仕組みから、CSSアニメーションのパフォーマンスを予測できるようになろう、という視点で、使うプロパティによってなぜパフォーマンスの差が生まれるのかを解明していきます。

Webページはデジタル絵画

ブラウザで表示されたWebサイトを、一枚のデジタル絵画に見立てて考えてみることにしましょう。

すると、アニメーションとは、

完成された絵画の一部を破壊し、新しく書き換えることを瞬時に何度も繰り返して行うこと

という解釈を与えることができます。

絵を描くことは、下書き、ペン入れ、色塗り、あとは別レイヤーで用意した部品を重ねる合成処理など、いくつかの段階を踏んで行われます。

下書きからやり直すとなると、当然時間がかかります。

このCSSプロパティの値を変えたときに、絵画がどこまで壊れるか、どの段階からやり直さなければならないか、を知ることで、CSSアニメーションのパフォーマンスを予測できるようになります。

CSSの描画手順

CSSが解釈された後に、Webページが描かれていく過程は、人間が絵を描く手順と大体同じです。

まずはレイアウト、各要素の位置と大きさを決めて、要素のボックスを並べていきます。

位置と構成が決まったら、次はペイント、実際に輪郭を描いたり、色を塗ったりしていきます。

最後に、レイヤーに分けて描いていた各部品を合成して、完成です。

1. Layout

Layout段階で適用されるプロパティは、いずれも値を変更すれば周囲の要素の位置や大きさにも影響が及ぶものです。

ここで登場したプロパティをアニメーションさせると、ドミノ倒しのように周辺のパーツが壊れてしまい、広範囲にわたって下書きからすべてやり直すことになります。

2. Paint

Paint段階で適用されるのは、各要素に閉じたスタイルなので、値を変えたときにレイアウトをやり直す必要はありません。

しかし、領域内のピクセル一つ一つを塗りつぶす処理が走ることになるので、Paintはレンダリングの中でも最も時間がかかります。

ペイント処理が必要になるプロパティの中でも、特に重いものが、box-shadowです。

影は無数のピクセルによって構成されていますが、そのピクセルごとに、ぼかし半径分だけ周囲のピクセルを調べて、色を混合する処理が発生します。

ぼかし半径が大きければ大きいほど、膨大な数のピクセルを調べることになるので、当然重くなります。

また、border-radiusと併用すると、曲線を滑らかにするためにさらにピクセルを細分化して濃淡をつける処理が発生するため、ますます重くなります。

box-shadowをアニメーションさせたい時は、あらかじめ擬似要素にbox-shadowを設定しておいて、その擬似要素のopacityをアニメーションさせると良いでしょう。

また、たくさんの要素に影をつけたい場合には、影をSVGやCSSでブラウザに描画させるのではなく、画像として用意するようにします。

3. Composite

最後のステップは、レイヤーの合成です。

ブラウザは、要素を適宜PaintLayerと呼ばれる別なレイヤーに振り分けて描画し、それらレイヤーを最後に重ね合わせることによって、最終的な画面を構築します。

ところで、アニメーションでCPUに負荷がかかると、CPUが他に行うべきJavaScriptなどの処理が進まなくなってしまうことがあります。

そこで、一定の条件を満たすPaintLayerは、GPUにレンダリング処理を外注するようにします。

このような特別なレイヤーを合成レイヤーと呼び、GPUに処理を外注することをハードウェアアクセラレーションと言います。

例えば、transformをアニメーションさせた場合、一時的に合成レイヤーが生成されます。

まず、translateさせたい要素だけを別のレイヤーに書き出し、それをGPUに送信します。

レイヤーにはtranslateさせる要素しか描画されておらず、そのほかの部分は透明です。

なので、レイヤーごとちょっとずらして重ね合わせるだけで移動したように見せることができます。

透明フィルムを上から貼り付けるようなイメージです。

仮に、transformではなく、toplefttransitionさせると、Layout自体が壊れてしまいます。

ブラウザは影響範囲をチェックし、再レンダリングは壊れた箇所だけにとどめようとしますが、それでもペイント処理は広範囲に及びます。

再レンダリング処理にかかった時間を計測すると、toptransformでは、これほどの違いが生まれます。

transformを使った場合は、別レイヤーに書き出すときにしかペイント処理が発生しないので、再レンダリングがかなり短い時間で済むことがわかります。

レイヤーの合成だけで済ませることで、処理は確かに軽くなりますが、レイヤーの存在自体は非常に重く、メモリを大量に消費するものです。

わざと3Dの指定を入れたり、will-changeプロパティを指定することで、レイヤーを無理やり生成させる手法は、スマートフォンではむしろ負荷が大きくなるリスクがあります。

続きは完全版で…

イベントでは、アニメーションでよく使われるCSSプロパティの負荷の大きさの比較や、clip-pathによる柔軟なアニメーション手法についても解説しています。

TechFeed Experts Night 14ページから、完全版のSlideArticleをぜひ見てみてください!