記憶に残ったことを忘れないために色々メモとしてのこしていきます

CSSのwill-changeについて

CSS Animation関連で will-change について2つのドキュメントを読んでみたので、その概要と一部和訳をしてみた。

mdn

概要

https://developer.mozilla.org/en-US/docs/Web/CSS/will-change
まず一文目で本質的な説明がされている。

The will-change CSS property hints to browsers how an element is expected to change. Browsers may set up optimizations before an element is actually changed. These kinds of optimizations can increase the responsiveness of a page by doing potentially expensive work before they are actually required.

will-changeを使うことで、ある要素がどのように変化するのか、をブラウザにヒントとして渡すことができるようだ。それによって、その要素が変わるに、ブラウザが最適なパフォーマンスをだすために準備ができ、最終的にUXの向上につながる、とのこと。これらのことから will-change について、以下の2つが推測される。

  • UIの表層に出てくるプロパティではないこと (例外として、「使い方の注意ポイント」の最後の要素を参照)
  • ブラウザのパフォーマンスに関与する


使い方の注意ポイント

Proper usage of this property can be a bit tricky:

will-change の使い方は「つまづきやすい (= tricky)」と表現しており、以下に具体的な留意点がリストアップされている。

  • will-change を多くの要素に使わないこと。ブラウザはすでにさまざまな最適化を自動的に行なっているが、 will-change のような「強い最適化」が多発するとブラウザのリソースを消費し、ブラウジング体験に悪い影響を与えてしまう。
  • will-change が不要になったら、削除すること。ブラウザが自動でおこなっている最適化は、対応が不要になれば最適化に関連する処理を削除するが、 will-change は明示的にエンジニア側で指定するものなので、利用する時だけ「生かす」ようにすること。
  • ある程度パフォーマンスの良いページにはwill-change を使って速度改善をしようとしないこと。つまり、他にもボトルネックとなっている改善点があるのであれば、そちらを優先的に対応すべきである。will-change は「last resort」とはこのことを言っている。
  • 対象の要素が変化する前にwill-change を付与すること。このプロパティが適用されると、ブラウザ側で最適化処理が走るため、変化の発生する要素にあらかじめ適用しておくこと。
  • will-change はビジュアルの点でもページに影響することがある。付与された要素は stacking-context が生成されるが、あらかじめ別のプロパティが付与されて stacking-context が生成されている場合は変化なし。(*stacking contextについてはこちらのページが非常にわかりやすくまとめられておりました)


使用例

JS経由で will-change のプロパティを設定しており、アニメーションが完了したら auto を指定することで、メモリを解放している。

var el = document.getElementById('element');

// 要素がホバーされたとき、will-change を設定する
el.addEventListener('mouseenter', hintBrowser);
el.addEventListener('animationEnd', removeHint);

function hintBrowser() {
  // アニメーションのキーフレームブロックで
  // 変更されるであろう最適化可能なプロパティ
  this.style.willChange = 'transform, opacity';
}

function removeHint() {
  this.style.willChange = 'auto';
}


Everything You Need to Know About the CSS will-change Property

https://dev.opera.com/articles/css-will-change-property/の和訳をしてみようと思う。2014年に書かれた記事だが、非常に濃い内容だった。

In a nutshell, Hardware Acceleration means that the Graphics Processing Unit (GPU) will assist your browser in rendering a page by doing some of the heavy lifting, instead of throwing it all onto the Central Processing Unit (CPU) to do. When a CSS operation is hardware-accelerated, it usually gets a speed boost as the page rendering gets faster.

("In a nutshell ..." と結論から完結に書いていて素晴らしい)
CSS transformsを使うと利用される "Hardware Acceleration"について言及されており、”Hardware Acceleration" を使うと重たい描画処理をGPUが行ってくれるので、処理が早くなる、と書かれているが、まあこの辺りは皆さんの記憶のどこかにあると思う。

Hardware acceleration (a.k.a. GPU acceleration) relies on a layering model used by the browser as it renders a page. When certain operations (such as 3D transforms) are performed on an element on a page, that element is moved to its own “layer”, where it can render independently from the rest of the page and be composited in (drawn onto the screen) later. This isolates the rendering of the content so that the rest of the page doesn’t have to be rerendered if the element’s transform is the only thing that changes between frames, and often provides significant speed benefits. It is worth mentioning here that only 3D transforms qualify for their own layer; 2D transforms don’t.

GPU acceleration (= 以降、GPU)は、レイヤーモデルを利用している。3D transformのような特定の処理が、ページ内の要素に対して行われる時、その要素は自分自身の「レイヤー」に移動する。そうすると、ページ内の他の要素とは独立して描画されることが可能になる。独立されるということは、この要素に変化が発生してもページ内の他の要素のレイアウトには影響せず、ムダな再描画を防ぐことができることを意味している。注意すべき点は、この仕様は3D transformationのみに適用され、2D transformationには適用されない。。昔、transform: translate3d(0, 0, 0); のようなおまじない的なものがあったが、それはこの理由に関連していたみたいだ。つまり強制的にGPUに処理をさせるためのハックですね。とはいえ注意する点として、GPUの処理はメモリを多く消費してしまう、特にモバイル端末 (追記: この記事が書かれたのが2014年なので、現在は定かではないが非力デバイスでの利用には要注意といったところかな)。

In order to avoid layer-creation hacks, a new CSS property has been introduced, that allows us to inform the browser ahead of time of what kinds of changes we are likely to make to an element, thus allowing it to optimize how it handles the element ahead of time, performing potentially-expensive work preparing for an operation such as an animation, for example, before the animation actually begins. This property is the new will-change property.

こうしたハックを防ぐために、 will-change が生まれた。

In order to avoid this delay, you can inform the browser about the changes some time before they actually happen. That way, it will have some time to prepare for these changes, so that when these changes occur, the element’s layer will be ready and the transform animation can be performed and then the element can be rendered and the page updated in quickly.

これから発生する「変化」についてブラウザに知らせておくことで、ブラウザ側は事前に準備をしてアニメーションのような処理もスムーズに行うことができる。

will-change: transform, opacity;

例えば、上記の記述によって、 transformopacity のアニメーションの最適化をおこなってくれる。

注意点

1. 特定の要素のみに適用すべきなので、下記のような記述はNG

*,
*::before,
*::after {
	will-change: all;
}


2. 準備する時間をブラウザに与えましょう
will-change の仕様についてもう一度確認をしましょう。

The will-change property is named like that for an obvious reason: informing the browser about changes that will occur, not changes that are occuring.

ウィル (will) という点がポイント。are ではなく will。つまり will-change を使うことで、我々が与えようとしている「変化」を最適なパフォーマンスで実行してください、とブラウザに指示をしている。さらに、 will-change という指示による最適なパフォーマンスをするためにはブラウザにとって、少し時間が要る点も忘れてはいけない。つまり変化が起こる直前に will-change を渡しても、効果はないどころか悪い影響をあたえてしまうこともある(不要なレイヤーを作成してしまう)。

.element:hover {
	will-change: transform;
	transition: transform 2s;
	transform: rotate(30deg) scale(1.5);
}

上記はブラウザに対して、このような指示をしていることと同じだ。「起こっている変化に対して、あらかじめメモリを確保して準備をしてくれ」変な日本語になってしまったが、 will-change の本来の用途は「これから起こる変化に対して、あらかじめメモリを確保して準備をしてくれ」なのだ。つまり、上のコードの例はナンセンスということになる。正しく使うためには、先に will-change を指定しておき、その後変化を与える必要がある。

例えば、要素がクリックされたときに rotate させたい場合、CSS的には :hover のあとに :active が適用されるとすると下記のようなコードが正しい will-change の使い方になる。hover時に will-change を適用させることで、その次のインタラクション (= :active) 実行までに transform の準備を指示することができる。

.element {
	/* style rules */
	transition: transform 1s ease-out;
}
.element:hover {
	will-change: transform;
}
.element:active {
	transform: rotateY(180deg);
}


Change が完了したら will-change は削除しよう

すでに述べたことだが、 will-change は、描画処理をスムーズに行うために明示的にメモリを確保しておく仕様なので、使い終わったあとは削除することで、いわゆるメモリを解放してあげる必要がある。スタイルシートに直書きしてしまうと、当然ながら削除することはできないためJSを使う必要がある。例外としてスタイルシートに書いても良いケースとしては、ユーザーが何度も操作する可能性のあるUI(メニューなど)でかつ要素数が少ないものが挙げられる。そもそも will-change を削除する必要はないため。