ぼかしは、ユーザーの注意をそらすのに最適な方法です。一部の視覚要素をぼかし、他の要素に焦点を合わせることで、ユーザーの視線を自然に誘導します。ユーザーはぼかし処理されたコンテンツを無視し、読めるコンテンツに集中します。たとえば、個々のアイテムにカーソルを合わせると詳細が表示されるアイコンのリストなどです。その間、残りの選択肢をぼかして、ユーザーを新しく表示された情報にリダイレクトできます。
要約
ぼかしのアニメーションは非常に遅いため、現実的な選択肢ではありません。代わりに、ぼかしの度合いが徐々に高くなる一連のバージョンを事前に計算し、それらの間でクロスフェードします。私の同僚の Yi Gu が、すべてを処理する ライブラリを作成しました。デモをご覧ください。
ただし、この手法を移行期間なしで適用すると、かなり違和感が生じる可能性があります。ぼかしをアニメーション化する(ぼかしなしからぼかしありに移行する)のは妥当な選択肢のように思えますが、ウェブでこれを試したことがある方は、アニメーションがスムーズではないことに気づいたでしょう。このデモで、高性能なマシンを使用していない場合にアニメーションがスムーズではないことがわかります。改善することは可能でしょうか?
問題
現時点では、ぼかしのアニメーションを効率的に動作させることはできません。ただし、技術的にはアニメーション ブラーではないものの、十分に良い回避策を見つけることはできます。まず、アニメーション ブラーが遅い理由を理解しましょう。ウェブ上の要素をぼかすには、CSS の filter プロパティと SVG フィルタという 2 つの方法があります。サポートの増加と使いやすさから、通常は CSS フィルタが使用されます。残念ながら、Internet Explorer をサポートする必要がある場合は、IE 10 と 11 がサポートしている SVG フィルタを使用するしかありません。IE 10 と 11 は CSS フィルタをサポートしていません。幸いなことに、ぼかしをアニメーション化するための回避策は、どちらの手法でも機能します。DevTools を見てボトルネックを探してみましょう。
DevTools で [ペイントの点滅] を有効にすると、点滅はまったく表示されなくなります。再描画が行われていないようです。これは技術的には正しいです。なぜなら、「再描画」とは、昇格された要素のテクスチャを CPU が再描画する必要があることを指すからです。要素がプロモートかつぼかし処理されるたびに、シェーダーを使用して GPU によってぼかしが適用されます。
SVG フィルタと CSS フィルタの両方で、畳み込みフィルタを使用してぼかしを適用します。畳み込みフィルタは、出力ピクセルごとに多数の入力ピクセルを考慮する必要があるため、かなりコストがかかります。画像が大きいほど、また、ぼかし半径が大きいほど、効果のコストは高くなります。
ここに問題があります。フレームごとにかなりコストのかかる GPU オペレーションを実行しているため、16 ミリ秒のフレーム バジェットを超過し、60 fps を大きく下回っています。
ラビットホール
では、このプロセスをスムーズに進めるにはどうすればよいでしょうか?手品を使えばいいんだ!実際のぼかし値(ぼかしの半径)をアニメーション化する代わりに、ぼかし値が指数関数的に増加するぼかしコピーを事前に計算し、opacity を使用してそれらの間でクロスフェードします。
クロスフェードは、不透明度のフェードインとフェードアウトが重なり合う一連の処理です。たとえば、4 つのぼかしステージがある場合、最初のステージをフェードアウトしながら、同時に 2 番目のステージをフェードインします。第 2 ステージの不透明度が 100% に達し、第 1 ステージの不透明度が 0% に達したら、第 2 ステージをフェードアウトしながら第 3 ステージをフェードインします。それが完了したら、第 3 段階をフェードアウトし、第 4 段階(最終版)をフェードインします。このシナリオでは、各ステージは合計所要時間の 4 分の 1 を占めます。視覚的には、実際のぼかしと非常によく似ています。
Google のテストでは、ステージごとにぼかし半径を指数関数的に増やすと、最良の視覚効果が得られました。例: 4 つのぼかしステージがある場合、各ステージに filter: blur(2^n) を適用します。つまり、ステージ 0: 1 ピクセル、ステージ 1: 2 ピクセル、ステージ 2: 4 ピクセル、ステージ 3: 8 ピクセルとなります。will-change: transform を使用して、これらのぼかしコピーをそれぞれ独自のレイヤに強制的に配置(「昇格」)すると、これらの要素の不透明度の変更が非常に高速になります。理論的には、これにより、ぼかし処理というコストのかかる作業を前倒しで実行できます。実際には、このロジックには欠陥があります。このデモを実行すると、フレームレートは 60 fps を下回ったままで、ブラーは以前よりも悪化していることがわかります。
DevTools を見ると、GPU はまだ非常にビジーで、各フレームを約 90 ミリ秒まで伸ばしていることがわかります。しかし、なぜこうなってしまったのでしょう。ぼかしの値は変更せず、不透明度のみを変更しているのに、なぜこのようなことが起こるのでしょうか?この問題も、ぼかし効果の性質にあります。前述のとおり、要素がプロモートされ、ぼかしが適用されると、効果は GPU によって適用されます。そのため、ぼかし値をアニメーション化しなくなっても、テクスチャ自体はぼかしが適用されていないため、GPU によってフレームごとに再度ぼかしを適用する必要があります。フレームレートが以前よりも悪くなったのは、単純な実装と比較して、GPU の処理量が増えたためです。ほとんどの場合、2 つのテクスチャが表示され、それぞれを個別にぼかす必要があるためです。
この方法は美しくありませんが、アニメーションを高速化できます。ぼかし対象の要素を宣伝するのではなく、親ラッパーを宣伝するようになります。要素がぼかしとプロモーションの両方の対象になっている場合、効果は GPU によって適用されます。これがデモの速度を低下させた原因です。要素がぼかし処理されているがプロモートされていない場合、ぼかしは代わりに最も近い親テクスチャにラスタライズされます。この例では、昇格された親ラッパー要素です。ぼかし画像が親要素のテクスチャになり、以降のすべてのフレームで再利用できるようになりました。これは、ぼかし効果を適用した要素がアニメーション化されておらず、キャッシュに保存することが実際に有益であることを知っている場合にのみ機能します。この手法を実装したデモをご覧ください。Moto G4 はこのアプローチをどう考えているのだろうか?ネタバレ注意: 素晴らしいと思っています。
GPU には十分なヘッドルームがあり、60 fps の滑らかな映像を実現しています。やったぞ!
製品化
デモでは、DOM 構造を複数回複製して、さまざまな強さでぼかすコンテンツのコピーを作成しました。本番環境でこの方法を使用すると、作成者の CSS スタイルや JavaScript に予期しない副作用が生じる可能性があるため、その仕組みについて疑問に思うかもしれません。おっしゃるとおりです。Shadow DOM!
ほとんどの人は Shadow DOM を「内部」要素を カスタム要素にアタッチする方法として考えていますが、これは分離とパフォーマンスのプリミティブでもあります。JavaScript と CSS は Shadow DOM の境界を越えることができないため、デベロッパーのスタイルやアプリケーション ロジックを妨げることなくコンテンツを複製できます。各コピーをラスタライズするための <div> 要素はすでに存在しており、これらの <div> をシャドーホストとして使用します。attachShadow({mode: 'closed'}) を使用して ShadowRoot を作成し、<div> 自体ではなく ShadowRoot にコンテンツのコピーを添付します。コピーが元のものと同じようにスタイル設定されるように、すべてのスタイルシートを ShadowRoot にコピーする必要があります。
一部のブラウザは Shadow DOM v1 をサポートしていません。そのようなブラウザでは、コンテンツを複製して、何も壊れないことを期待するだけです。ShadyCSS で Shadow DOM ポリフィルを使用することもできますが、ライブラリには実装していません。
これで、Chrome のレンダリング パイプラインをたどる旅を終え、ブラウザ間で効率的にぼかしをアニメーション化する方法を理解できました。
まとめ
このような効果は、軽率に使用すべきではありません。DOM 要素をコピーして独自のレイヤに強制的に配置するため、ローエンド デバイスの限界までプッシュできます。すべてのスタイルシートを各 ShadowRoot にコピーすると、パフォーマンスのリスクも発生する可能性があります。そのため、LightDOM のコピーの影響を受けないようにロジックとスタイルを調整するか、ShadowDOM 手法を使用するかを決定する必要があります。ただし、この手法が有益な投資となる場合もあります。GitHub リポジトリのコードとデモをご覧ください。ご不明な点がございましたら、Twitter でご連絡ください。