ぼかしは、ユーザーのフォーカスをリダイレクトするのに適した方法です。一部の視覚要素をぼかし、他の要素をフォーカスにすると、ユーザーの視線が自然に誘導されます。ユーザーはぼかしを入れたコンテンツを無視し、代わりに読めるコンテンツに集中します。たとえば、アイコンのリストにカーソルを合わせると、個々のアイテムの詳細が表示されます。この間、残りの選択肢はぼかし、新たに表示された情報にユーザーをリダイレクトできます。
要約
ぼかしをアニメーション化するのは非常に時間がかかるため、現実的ではありません。代わりに、ぼかしが強くなっていく一連のバージョンを事前に計算し、それらをクロスフェードします。同僚の Yi Gu が、すべてを処理するライブラリを作成しました。デモをご覧ください。
ただし、この手法は移行期間なしで適用すると、非常に不自然な印象を与える可能性があります。ぼかしをアニメーション化(ぼかしなしからぼかしありへの遷移)するのは妥当な選択肢のように思えますが、ウェブでこの操作を試したことがあれば、アニメーションがスムーズに動作しないことに気づいたはずです。このデモに示すように、高性能なマシンがなければ、アニメーションはスムーズに動作しません。改善すべき点はありますか?
問題
現時点では、ぼかしのアニメーションを効率的に行うことはできません。ただし、見た目は十分でも、技術的にはアニメーション ブラーではない回避策があります。まず、アニメーション ブラー処理が遅くなる理由について説明します。ウェブ上の要素をぼかすには、CSS filter
プロパティと SVG フィルタの 2 つの方法があります。サポートの拡大と使いやすさの向上により、通常は CSS フィルタが使用されます。残念ながら、Internet Explorer をサポートする必要がある場合は、SVG フィルタを使用するしかありません。IE 10 と 11 は SVG フィルタをサポートしていますが、CSS フィルタはサポートしていません。幸い、ぼかしをアニメーション化する回避策は、どちらの手法でも機能します。では、DevTools でボトルネックを見つけてみましょう。
DevTools で [Paint Flashing] を有効にすると、フラッシュはまったく表示されなくなります。再描画が行われていないようです。これは技術的には正しい表現です。なぜなら、「再描画」とは、CPU が昇格した要素のテクスチャを再描画する必要があることを指します。要素が昇格されてぼかされると、GPU によってシェーダーを使用してぼかしが適用されます。
SVG フィルタと CSS フィルタの両方で、ぼかしを適用するために畳み込みフィルタが使用されます。畳み込みフィルタは、出力ピクセルごとに複数の入力ピクセルを考慮する必要があるため、かなり高価です。画像が大きくなるほど、またはぼかし半径が大きくなるほど、効果のコストは高くなります。
これが問題の原因です。フレームごとにかなり負荷の高い GPU オペレーションを実行しているため、フレーム バジェット 16 ミリ秒を使い果たし、60 fps を大きく下回る結果になっています。
ラビットホール
では、このプロセスをスムーズに進めるにはどうすればよいでしょうか?手品を使うことができます。実際のぼかし値(ぼかしの半径)をアニメーション化する代わりに、ぼかし値が指数関数的に増加するぼかしコピーをいくつか事前に計算し、opacity
を使用してそれらの間でクロスフェードします。
クロスフェードは、不透明度のフェードインとフェードアウトが重なり合う一連のアニメーションです。たとえば、4 つのぼかしステージがある場合、最初のステージをフェードアウトしながら、同時に 2 番目のステージをフェードインします。2 番目のステージが 100% の不透明度に達し、1 番目のステージが 0% に達すると、2 番目のステージがフェードアウトし、3 番目のステージがフェードインします。その後、3 番目のステージをフェードアウトし、4 番目の最終バージョンをフェードインします。このシナリオでは、各ステージは合計所要時間の 1/4 を占有します。視覚的には、実際のアニメーション ブラーにもよく似ています。
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
手法を使用するかを判断する必要があります。ただし、Google の技術は投資に値することもあります。GitHub リポジトリのコードとデモをご覧ください。ご不明な点がございましたら、Twitter でお問い合わせください。