この記事について
背景
みなさんはフェードイン/フェードアウトアニメーションを実装する時はどのように実装しますか?
- CSSの
transition
プロパティやanimation
プロパティでopacity
プロパティを変化させる - jQueryを使用していれば
fadeIn()
/fadeOut()
メソッドを使用する
など、色々方法が出てくるかと思います。ではどの方法が最適でしょうか?
筆者は普段レガシー環境でエンハンス業務に従事していますが、なんの気なしに既存コードを流用して、後者のjQueryのメソッドを使ってアニメーションを実装しようとしていました。そこで先輩エンジニアに「jQueryのアニメーションを使うとCPUで動くからパフォーマンスがちょっと悪い、できればGPUで動くCSSアニメーションを使いたい」の一言をいただき、Webアニメーションの奥深さを知りました。
伝えたいこと
この記事では、先輩エンジニアの一言から筆者が調べたことをまとめています。
ではどの方法が最適でしょうか?
この問いに答えられるような、Webアニメーションを実装する際に一つの観点を与えるものになっていればと思います。
CPU処理とGPU処理
ここで冒頭の先輩コメントに登場した、CPU処理とGPU処理について簡単に特徴をまとめます。
CPU
- CPU(Central Processing Unit)とは、コンピューターの制御・計算(演算)を行う中央演算処理装置。コア数により扱える処理の数が増えるが、GPUよりは少ない。大量の処理を行うのには向かない。一つの複雑な処理を行うのに向いている
- JavaScriptやjQueryアニメーションはCPU上で動作する
GPU
- GPU(Graphics Processing Unit)とは、画像処理装置のことでCPUに比べてコア数が多く、並列作業が得意。定型的で、膨大な処理を行うのに向いている
- CSSアニメーションの特定のプロパティは、GPU上で動作する。CPUで動くアニメーションよりも滑らかに動作する
GPUで動くCSS3プロパティについて
transition
プロパティやanimation
プロパティで設定した全てのプロパティがGPU上で動作するわけではなく、transform
やopacity
などの一部プロパティが対象になります。
詳しくは後述のCSSプロパティのコストを参照。
CSSプロパティのコスト
再レンダリングのフローやレイヤの概念を知ることで、なぜGPU処理の方がパフォーマンスがよいのか理解できます。
要素の再レンダリングフロー
アニメーション時において、要素が再レンダリングされる際は、以下のフローが発生します。
- style(スタイル再計算)
- layout(要素の配置・リフロー)
- paint(描画・リペイント)
- composite(レイヤ合成)
1と4は必ず行われますが、GPU上で動くプロパティは2と3の工程を省くことができ、低コストとなるためスムーズなアニメーションを実現できます。
GPUのレイヤ概念
GPUではレイヤ概念というものがあり、レイヤを作成、作成したレイヤごと移動させる(アニメーションさせる)ことで、2と3の工程を省いています。
まとめると、
- CPUレンダリングの場合:要素の位置が変更される度にpaint処理が走る
- GPUレンダリングの場合:レイヤ概念を用いて、レイヤを移動(2(layout),3(paint)処理を省ける)させて4(composite)が行われる
といった違いがあり、GPUレンダリングの方が低コストな理由がわかります。
CSSプロパティにより異なるコスト
プロパティの特性により、再レンダリングするコストがそれぞれ異なってきます。
- 全ての過程を辿るため、コストが高いプロパティ
-
left
,max-width
,border-width
,margin-left
,font-size
など
-
- 2(layout)の過程は省かれるプロパティ
-
color
など
-
- 1(style)と4(composite)のみのためコストは低いプロパティ
-
transform
,opacity
など
-
GPU上で動作しないCSSプロパティも動作するプロパティと一緒に使えばレイヤにのせられる場合があります。
例えば、animation
プロパティでkeyframs
を作成する際は、アニメーション対象のプロパティを個別で設定するよりも、合わせて設定した方がパフォーマンスが向上する場合があります。
レイヤをあらかじめ作成するwill-change
プロパティ
前述のGPUレンダリングの場合レイヤが生成されるタイミングはアニメーション開始の直前になりますが、事前にレイヤを生成しておく方法もあります。あらかじめブラウザに変化することを伝えているため実際に変化があった時にスムーズに動作できます。
MDNにも警告が載っていますが、事前にレイヤを作成する方法は、GPUで動作するCSSアニメーションを使ってもカクつく場合など、やむを得ない場合にのみ使用すべきです。乱用すると、事前に大量のレイヤを生成・グラフィック情報保持していることになり、メモリーを消費するためかえってパフォーマンスが悪くなる可能性があります。
事前にレイヤを作成しておくため、ハック的にtransformZ(0)
プロパティを使う方法もあるようですが、will-chang
プロパティと同様の理由で、必要ない限りは使用しない方がよいでしょう。
デベロッパーツールでの確認(Chrome)
再レンダリングはどの処理が走っているのか、レイヤ生成されてるのか、についてはデベロッパーツールから確認することができます。
再レンダリングの確認
デベロッパーツール > パフォーマンス > 記録ボタン実行 > イベントログを確認することで、レンダリング処理を確認できます。
また、デベロッパーツール > その他のツール > レンダリング > ペイント点滅にチェックするとリペイントされている要素がわかります。以下はレイヤが作成されないleft
プロパティを用いてサンタ画像を左から右へ移動させています。移動の度にリペイントの処理が入っているのがわかります。
.santaImg { // サンタ画像の要素
position: absolute;
width: 200px;
height: auto;
animation: animation-santa;
animation-duration: 2s;
animation-iteration-count: infinite;
}
@keyframes animation-santa {
0% {
left: 0;
}
100% {
left: 300px;
}
}
レイヤ確認
デベロッパーツール > その他のツール > レイヤを選択するとレイヤパネルが開きます。以下の例ではtransform: translateX()
でサンタ画像を移動させています。レイヤパネルではページ全体の枠の中にサンタ画像の部分に枠がつき、レイヤになっていることがわかります。
.santaImg { // サンタ画像の要素
width: 200px;
height: auto;
animation: animation-santa;
animation-duration: 2s;
animation-iteration-count: infinite;
}
@keyframes animation-santa {
0% {
transform: translateX(0);
}
100% {
transform: translateX(300px);
}
}
このように、レンダリング状況を確認することができます。位置移動のアニメーションをleft
プロパティにした場合は、リペイント処理が走り、レイヤ枠は表示されません。transform
プロパティの場合にはリペイント処理は走らずに、レイヤ枠が表示されます。
また、left
プロパティの場合でもopacity
プロパティと一緒に使用した場合やwill-change
プロパティを使用した場合は、リペイント処理は走らず、レイヤ枠が表示されます。
Web Animations APIの利用
ここまで「アニメーションは極力GPUで動くCSSアニメーションで実装する方がよい」というような内容を書いてきましたが、時にはJavaScript側で動的な値を取得し、アニメーションをつけたい場合が出てくるかと思います。
こういった時に Web Animations APIを使用すれば、JavaScript操作と親和性高く、かつ通常のCSSアニメーションと同じように動作するので、GPU側でアニメーションを動作させることが可能です。
Web Animations APIは外部ライブラリではないため、読み込み負荷かがない点でもパフォーマンス的に優れています。
まとめ
パフォーマンスに留意したアニメーションの実装
今回調べたことをもとに、筆者は以下の観点を持つようにしています。
- アニメーション処理はハードウェアアクセラレーション(グラフィック関連の処理をGPUに任せること)を目指す
- 可能な限り、GPU上で動作するCSSプロパティを使用して実装する
- GPU上で動作しないCSSプロパティも動作するプロパティと一緒に使えばレイヤに乗せてくれるので、
animation
プロパティのkeyframes
などはあまり細かく区切らずに書く
- JavaScriptから動的な操作が必要な場合はWeb Animations APIなど標準のAPIを使用する
参考資料