※will-changeは編集時点(2016/07/27)でCandidate Recommendationの状態です。仕様変更による挙動差異などがあれば適宜コメントを頂けるとありがたいです。
TL;DR
- 使いどころ
- JavaScriptでアニメーションを行う際
- CSS animation, transitionを
:hover
等で遅延実行する際 - などの、初期値として変形やアニメーションの値を持たない要素を特定の要因で実行する際に効果を発揮する
- ユースケースによって、常に指定するか、動かす直前から指定して終了時に解除するかを判断する
- 最適化はUAごとに異なっており、かつ指定したからといって必ず恩恵が得られるものではない
- "おまじない"的な使い方も有りだが、本当にパフォーマンスが必要な場面で有効かどうかはブラウザの実装を調べるのがてっとり早い
前提知識
- Webブラウザのレンダリングの仕組み
- 合成レイヤーとその確認方法
- Accelerated Rendering in Chrome: The Layer Model - HTML5 Rocks
- ほか、"ブラウザ 合成レイヤー"とか"ブラウザ GPU"とかでググる
- 描画周りのプロファイリング、Dev Tools
Spec
Specがかなり分かりやすく書かれているので、上から下まで一読することをお勧めする。
個人的に分かりづらい、と思った点に関しては考察に合わせて記す。
ブラウザの実装調査
一先ず、ソースが調べやすいChrome, Firefoxで調査した。
- Google Chrome 53.0.2785.16
- Firefox 49
- https://hg.mozilla.org/mozilla-central/ でソースが見づらかったので"FIREFOX_AURORA_49_BASE"のタグが切られたcommit hashをもとにgithubで参照した
- 実装の詳細に関しては https://dxr.mozilla.org/mozilla-central/ で調べた
また、以下の内容では特にパフォーマンスに関連する項目のみを記載する。例えば、will-change
の値としてStacking Contextを生成するようなプロパティを指定した際は、その要素はStacking Contextを生成しなければならないが、そういった実装箇所に関しては記載しない。
Chrome
-
will-change
への指定値をStyleWillChangeData
として保持し、各実装から参照される - transform, box offsets(top, left, bottom, right), opacityが指定された際に、Composited Layerが生成される
- contentsが指定された際に、その要素とその要素以下すべての要素に
SubtreeWillChangeContents
というフラグがたち、その要素群の中のいずれかの要素がアニメーションしている場合に、その要素に対して- Composited Layerが生成される
- Squashing(複数のRender Layerを1つのGraphic Layerへまとめること)が行われなくなる
- transformが指定された際に、ラスタライズ済みのレイヤのスケールの再計算を行わなくなる
- この実装はPictureLayerのみ。何となくやってることは分かったが、ちょっと説明する自信が無いので詳しい方がいればご教示いただきたい
References
- StyleWillChangeData.h
- ComputedStyle.cpp#909, CompositingReasonFinder.cpp#89
- ComputedStyle.h#1049, CompositingReasonFinder.cpp#86
- picture_layer_impl.cc#926, picture_layer_impl.cc#432
Firefox
-
will-change
への指定値をbit fieldとして保持し、各実装からはビット演算で参照される - transform, opacityが指定された際に、Composited Layer生成される
- scroll-positionが指定された際に、その要素が持つスクロールを行う(可能性がある)扱いになり、Composited Layerが生成される
ほか気になった点として、nsDisplayListBuilder::AddToWillChangeBudget
というメソッドで、will-change
が指定された要素をComposited Layerにするためのコストが規程の範囲内に収まるかどうかを判定していた。
この判定がfalse
で返る場合、Composited Layerは生成されない。
References
- nsRuleNode.cpp#L6216-L6263, nsRuleNode.cpp#L5300-L5317
- ActiveLayerTracker.cpp#L424-L434
- nsGfxScrollFrame.cpp#L4836-L4861
検証
上記の実装を踏まえつつ、will-change
への各値を指定した際の挙動をざっくりと確認するためのサンプルを用意した。
サンプルでは、will-change
を指定していない要素, 指定した要素の2つを並べ、それぞれの要素に対してwill-change
へ指定したプロパティをJavaScriptでアニメーションさせている。(contents
指定のサンプルに関しては、内包するテキストを追加するようにした)
Timeline Profilingの実行結果, Composited Layerの有無, ほか気になった点に関して各ブラウザで確認した結果を合わせて記載するが、詳細な結果は各自Dev Tools等を用いて確認いただきたい。
scroll-position
browser | Timeline Profiling | Composited Layer |
---|---|---|
Chrome | ほぼ同一 | 生成なし |
Firefox | 指定なし要素は、動き始めにfpsの落ち込みがある | 指定した要素は常に有り, 指定なし要素はアニメーション時に有り |
contents
browser | Timeline Profiling | Composited Layer |
---|---|---|
Chrome | ほぼ同一 | 生成なし |
Firefox | ほぼ同一 | 生成なし |
transform(2D)
browser | Timeline Profiling | Composited Layer |
---|---|---|
Chrome | 指定した要素はRasterizerの実行頻度が激減 | 指定した要素は常に有り |
Firefox | 指定なし要素は、動き始めにfpsの落ち込みがある | 指定した要素は常に有り, 指定なし要素はアニメーション時に有り |
transform(3D)
browser | Timeline Profiling | Composited Layer |
---|---|---|
Chrome | 双方ともRasterizerの実行頻度が激減 指定なし要素は、動き始めにfpsの落ち込みがある |
指定した要素は常に有り, 指定なし要素はアニメーション時に有り |
Firefox | 指定なし要素は、動き始めにfpsの落ち込みがある | 指定した要素は常に有り, 指定なし要素はアニメーション時に有り |
left
browser | Timeline Profiling | Composited Layer |
---|---|---|
Chrome | 指定した要素はRasterizerの実行頻度が激減 | 指定した要素は常に有り |
Firefox | ほぼ同一 | 双方ともアニメーション時に有り |
opacity
browser | Timeline Profiling | Composited Layer |
---|---|---|
Chrome | 指定した要素はRasterizerの実行頻度が激減 | 指定した要素は常に有り |
Firefox | ほぼ同一 | 指定した要素は常に有り, 指定なし要素はアニメーション時に有り |
各検証には以下のブラウザを使用した。
- Mac Google Chrome 52.0.2743.82 (64-bit)
- Mac Mozilla Firefox 47.0.1
- Mac Safari 9.1.2 (11601.7.7)
※調べたソースとまったく同一のバージョンでない点に注意(手元でビルドまではしていないので)
※ここにも書いたが、Edgeはあまりプロファイラが役に立たなそうなので調べていない。詳しい方にご教示いただきたい。
考察
変化をさせる要素の、変化をさせるCSSプロパティを確実に指定し、その副作用を必ず留意する
CSS Will Change Module Level 1#dont-global
will-change
は変化させないCSSプロパティを指定しても何も意味が無い。
また、値として指定したCSSプロパティによっては、そのCSSプロパティ自体がその要素に指定されていなくともStacking Context, Containing Blockを発生させる点にも注意する必要がある。
常に指定し続けるのではなく、要素を変化させる前に猶予を持って指定し、変化終了後に指定を外す
CSS Will Change Module Level 1#dont-waste
前述したブラウザ実装で考えると、will-change
を指定した際にComposited Layerが生成されるため、そのレイヤを生成する, また保持する分だけのマシンリソースをさらに消費する。変化のない要素に対してwill-change
を指定すると言うことは、そのリソースを常に無駄にし続けることになる。
特にモバイル端末では、豊富なリソースを確保できない場合もあるため、こういった省力化を行うべきである。
ただし、その都度will-change
の指定を行うことは実装において煩雑さが増えるため、以下のようなルールでwill-change
を指定すればよいと考えている。
- スプラッシュ画面、ページ表示時に1度しか動かないようなものは、動作開始時、終了時に
will-change
の指定値を切り替える - ページ表示中、常に動き続ける、メニューなどで頻繁に動くようなものは、CSS上に予め指定する
- ただし後述の通り、指定した瞬間から最適化が実行されるようなCSSプロパティが要素に指定されている場合は、
will-change
の指定自体が不要になりうる
- ただし後述の通り、指定した瞬間から最適化が実行されるようなCSSプロパティが要素に指定されている場合は、
ただ、どちらにせよ動的なスタイル指定というのは悩ましい部分はあり、Specに記載されている例で言うと
.element { transition: opacity .2s; opacity: 1; }
.container:hover > .element { will-change: opacity; }
.element:hover { opacity: .3; }
は正しいが、仮に.container
が特殊なレイアウトをしている場合に:hover
の範囲から外れ、.element
はwill-change
の効力を失うということもあり得る。
JavaScriptで考えてみると
element.style.willChange = "opacity";
requestAnimationFrame(() => {
element.style.opacity = 0;
element.style.willChange = null;
});
のようなに、will-change
指定後にrequestAnimationFrame
を実行し最適化が終わっていることを見越してからアニメーションを開始するような実装になりそうだ。
ブラウザによっては、一部のCSSプロパティを指定した瞬間から最適化するものがあるため、そのような要素に対してさらに同じプロパティを値としたwill-change
を指定するのは、"現状では"必要はなさそう
例えば、Webkit系のブラウザでよく言われるtransform: translate3d(0, 0, 0);
を指定するとComposited Layerを生成が生成されるため要素を動かした際にガクガクしなくなる、などのトリックがこれにあたる。
このような要素に対して、さらにwill-change: transform;
を指定することは意味がない。
"現状では"と書いたが、今後will-change
指定時だけ働くような最適化が生まれた場合はこの限りでは無い。
will-change
難しい…、というか扱いづらい。
が、用法用量を守れば確実に効果があり、今までのようなトリックで暗黙的なComposited Layerを生成して誤魔化さなくても良くなる点に関しては良いかもしれない。