単刀直入に本題に入りましょう。モダンブラウザは4つの項目を手軽にアニメーションすることができます。位置(position)、大きさ(scale)、回転(rotation)、透明度(opacity)です。もし、それ以外のものをアニメーションするなら、それはご自分の頑張り次第ですが、絹のようになめらかな60fpsの動画を実現できる可能性は少ないでしょう。
同じゆっくりとした動きのアニメーションが並んだ動画を見てみましょう。
一方はtransformが終わっていますが、もう一方は終わっていません。どのような違いがあるかわかったところで、なぜこのような違いがあるのか考えてみましょう。
DevToolsにおけるDOMツリーの描画
あなたがChrome DevToolsのTimelineを使うとき、これと似たパターンを見るはずです。
ブラウザが行うプロセスはとてもシンプルです。要素に適用するスタイルを計算します(Recalulate Style)、それぞれの要素のジオメトリと位置を生成します(Layout)、レイヤーへそれぞれの要素のピクセルを埋めます(Paint Setup と Paint)、そしてスクリーンに対してそれらを描画します(Composite Layers)。
スムーズなアニメーションを実現するのに必要なのは処理をさせないことです。そのための一番いい方法は、transformとopacityのプロパティだけを変更することです。タイムラインのウォーターフォールの上にあればあるほど、ブラウザの処理する量が多くなります。
このアドバイスはだいたいのブラウザに当てはまります。Chrome、Firefox、Safari、そしてOperaはtransformとopacityの全てにハードウェアアクセラレーションを適用します。残念ながらInternet Explorer 10以上でハードウェアアクセラレーションが適用される条件は明らかになっていません。しかしInternet Explorer 11のF12開発者ツールがリリースされれば、この条件が分かるかもしれません。
レイアウト変更を伴うプロパティのアニメーション
要素に変更を加えると、ブラウザはその変更の影響を受ける全ての要素のジオメトリ(位置と大きさ)の計算を必要とするレイアウトを行います。つまりある要素を変更したとき、その他の要素もジオメトリの再計算が必要になるのです。例えば <html> 要素の幅を変更すると、その子要素の多くが影響を受ける事になります。また、はるか下層のツリーで、ある要素が他の要素へはみ出したり何かしらの影響を与えた結果、その変更がずっと上層のツリーのレイアウト再計算を発生させる事もあります。
可視要素のツリーが大きい程レイアウト計算量も多くなるため、レイアウト再計算を誘発するようなプロパティをアニメーションすることを回避するのに腐心しなければなりません。
<html>要素、<body>要素のクラスでアプリの状態を管理しようとしていますか? その要素の変更の際、ブラウザはスタイルとレイアウトの計算をやり直す必要があります。スタイル計算、レイアウトの想定しない再実行に気を付けてください。アニメーションがないものでも、負荷が高い場合がありますよ!
以下のよく使われているCSSプロパティは、変更時にレイアウト更新を伴います。
レイアウトに影響を及ぼすプロパティ
| width | height |
| padding | margin |
| display | border-width |
| border | top |
| position | font-size |
| float | text-align |
| overflow-y | font-weight |
| overflow | left |
| font-family | line-height |
| vertical-align | right |
| clear | white-space |
| bottom | min-height |
Source: http://goo.gl/lPVJY6
描画をともなうアニメーションのプロパティ
要素を変更することにより描画が引き起こされることがあります。モダンブラウザでは描画の大部分がソフトウェアのラスタライザーにより行われます。あなたのアプリ上で要素がどのレイヤーに分類されるかによって、変更された要素以外の他の要素にも描画が引き起こされる可能性があります。
もしレイヤーに対する知識が深くないのであれば、Tom Wiltziusが書いたレイヤーについての概論を読んでおくべきでしょう。
描画を引き起こす要素はたくさんありますが、そのうち有名なものを以下に列挙します。
描画に影響を与えるスタイル
| color | border-style |
| visibility | background |
| text-decoration | background-image |
| background-position | background-repeat |
| outline-color | outline |
| outline-style | border-radius |
| outline-width | box-shadow |
| background-size |
Source: http://goo.gl/lPVJY6
上記のプロパティで要素をアニメーションさせた場合、再描画が発生し、それらの要素が属するレイヤーがGPUに転送されます。携帯端末においては、CPUがデスクトップPCのものより劣っているため、描画処理に特に時間がかかることを意味します。また、CPUとGPU間の転送量も限られているため、テクスチャの更新にも時間がかかります。
コンポジションを伴うアニメーションプロパティ
描画を誘発しそうで誘発しないCSSプロパティとして、opacityがあります。opacityの値に変更を加えると、低いアルファ値で要素テクスチャが描画されることで、コンポジティング間のGPUがハンドルされます。そして、テクスチャは同一レイヤーに合成されないと動作しません。もしそれらが異なるレイヤーにグルーピングされると、GPUレイヤーに含まれるopacityに変化があった他の要素も誤ってフェードされてしまうでしょう。
BlinkやWebKitのブラウザにおいて、opacityに加えてtransitionやanimationプロパティを要素に付与すると、新しいGPUレイヤーが生成されますが、開発者はtranslateZ(0)やtranslate3d(0,0,0)を使って強制的にGPUレイヤーの生成を行います。レイヤーの生成を強制的に行うと、アニメーション開始時(レイヤーの作成と描画は微細な変更にはならず、アニメーションの開始を遅らせます)にすぐ描画可能になり、アンチエイリアスの変化による外観の変化はありません。レイヤーのプロモートは控えめに行うべきであり、もし過剰に行ってしまうと描画レイヤーが多数生成されてしまい、ジャンクを引き起こしてしまいます。
Chromeにおいて、ルートではない非透過レイヤーはサブピクセルのアンチエイリアスではなくグレイスケールのアンチエイリアスを使い、アンチエイリアスが突然発生すると、とても目立ちうるものです。アニメーションの開始よりも前にプロモートしておきたいのであれば、先にやっておきましょう。
要素のtransformを変えることは、つまるところ位置、回転、縮尺を変えることです。多くの場合、positionはleftとtopプロパティが設定されることでアニメーションが発生します。この問題は上に示したように、leftもtopも再レイアウトを引き起こしてしまい、非常にコストがかかることです。よりよい方法としてはtranslateを使うことであり、translateは再レイアウトを引き起こしません。
Chrome CanaryとSafariにおいては、メインスレッドでは使われないアニメーションフィルタを使うことが可能であり、アクセラレートされ一般的にパフォーマンスが良いです。しかしInternet ExplorerやFirefoxではサポートされておらず、問題を起こしてしまうでしょう。
命令的アニメーション vs 宣言的アニメーション
開発者はアニメーションをJavaScript(命令的:imperative)で行うか、CSS(宣言的:declarative)で行うか決めなければならないことがあります。
命令的
命令的なアニメーションのいちばんのメリットは、同時に、いちばんのデメリットを引き起こすこともありえます。アニメーションはブラウザのメインスレッド上のJavaScriptで実行されています。メインスレッドはすでに別のJavaScript(スタイル計算、レイアウトや描画)でいっぱいいっぱいです。スレッドの競合がよく起こります。これは、アニメーションのフレームがコマ落ちする可能性がかなり高くなります。
JavaScriptでのアニメーションは、ほんとうに多くの制御ができます。再生、一時停止、逆再生、中止やキャンセルも、お手のものです。パララックススクローリングのように、JavaScriptでしか実現できない効果もいくつかあります。
宣言的
宣言的アプローチとは、transitionやanimationをCSSで記述することです。いちばんのメリットは、ブラウザがアニメーションを最適化できることです。必要であればレイヤーも作れますし、前述したようにメインスレッドでの操作の実行をやめることもできます。これは、よい点です。多くの人にとって、CSSの主なデメリットは、JavaScriptの表現力がないことです。有意義な方法でアニメーションを組み合わせることは、非常に難しいです。つまり、アニメーションをオーサリングすることは、複雑でエラーが出やすくなるのです。
展望
Web標準が発達するにつれて、 アニメーション周りの制約の一部はなくなるでしょう。workerを用いたJavaScriptアニメーションの実行というコンセプトを研究しようというIan Vollick(Google)による提案があります。これは、レイアウトまたはスタイルの再計算を引き起こしません。
もっと宣言的なアニメーションの方法に、Web Animations仕様があります。Jake Archibaldの長々とした紹介記事もありますよ。
まとめ
なめらかなアニメーションは素晴らしいウェブを体験させる根幹です。レイアウトや描画を引き起こすプロパティをアニメーションさせるのは避けるべきです。レイアウトと描画はどちらも処理が重く、フレーム落ちを起こす可能性があるからです。宣言的なアニメーションは事前にブラウザに最適化することができるので、命令的なアニメーションより良いとされています。
transformはGPUに処理を任せることが可能な、アニメーションに最も適したプロパティなので、それらだけで表現出来るのであれば、そうするべきでしょう。
- opacity
- translate
- rotate
- scale
将来、スムーズなアニメーションをJavaScriptと同じくらいの表現で実装でき、重いコストを考えることなく制約もなしに、CSSアニメーションと同じくらい最適化されたアニメーションを実現するための新しい方法が出てくるでしょう。