ぼかしをアニメーション化する

ぼかしはユーザーの注意をそらす優れた方法です。一部の視覚要素がぼやけて見えるようにして、他の要素にフォーカスを合わせたままにすることで、ユーザーのフォーカスを自然に指示できます。ユーザーはぼかしを入れたコンテンツを無視し、読みやすいコンテンツに集中します。一例として、アイコンのリストにカーソルを合わせると、個々のアイテムの詳細が表示されます。その間は、残りの選択肢をぼかし、新しく表示された情報にユーザーをリダイレクトできます。

要約

ぼかしのアニメーション化は非常に遅いため、実際には選択肢にありません。代わりに、ぼかしが入った一連のバージョンを事前に計算し、クロスフェードします。私の同僚の Yi Gu が、あらゆることをできるようにライブラリを作成しました。デモをご覧ください。

ただし、この手法は、移行期間を設けないまま適用すると、非常に不快なものになる可能性があります。ぼかしのアニメーション化(ぼかしなしからぼかしありへの移行)は理にかなっているように思えますが、ウェブでこれを行おうとしているなら、このデモが高性能なマシンをお持ちでない場合に示しているように、アニメーションは滑らかではないことにお気づきでしょう。改善すべき点はありますか?

問題

マークアップは CPU によってテクスチャに変換されます。テクスチャは GPU にアップロードされます。GPU はシェーダーを使用して、これらのテクスチャをフレームバッファに描画します。ぼかしはシェーダーで行われます。

現時点では、ぼかしを効率よく処理することはできません。ただし、十分に良好に見え、技術的にはアニメーションによるぼかしではない回避策を見つけることはできます。まず、アニメーションによるぼかしが遅い理由を理解しましょう。ウェブ上の要素にぼかしを入れるには、CSS filter プロパティと SVG フィルタの 2 つの方法があります。サポートが強化され、使いやすくなったため、通常は CSS フィルタが使用されます。ただし、IE 10 および IE 11 では SVG フィルタがサポートされますが、CSS フィルタはサポートされていないため、Internet Explorer をサポートする必要がある場合、SVG フィルタを使用するしかありません。ただし、ぼかしをアニメーション化するための回避策は、どちらの手法でも機能します。では DevTools を見て ボトルネックを見つけましょう

DevTools で [ペイント フラッシュ] を有効にすると、点滅はまったく表示されません。再ペイントは行われていないようです。「再ペイント」とは、CPU がプロモートされた要素のテクスチャを再ペイントしなければならないことを指します。これは技術的に正しく言えます。要素がプロモートされ、ぼかしが加えられると、GPU がシェーダーを使用してぼかしを適用します。

SVG フィルタと CSS フィルタはどちらも、畳み込みフィルタを使用してぼかしを適用します。畳み込みフィルタは、出力ピクセルごとに多数の入力ピクセルを考慮する必要があるため、かなりの費用がかかります。画像が大きいほど、またぼかしの半径が大きいほど、その影響は大きくなります。

問題があるのは、フレームごとにかなり高コストの GPU オペレーションを実行していることです。フレーム バジェットが 16 ms を消費するため、最終的に 60 fps を大幅に下回ります。

ウサギの穴を開ける

これをスムーズに行うにはどうすればよいでしょうか。そうこなくっちゃだよ!実際のぼかし値(ぼかしの半径)をアニメーション化するのではなく、ぼかし値が指数関数的に増加する複数のぼかしコピーを事前に計算してから、opacity を使用してクロスフェードします。

クロスフェードは、不透明度が重なる一連のフェードインとフェードアウトです。たとえば、ぼかしステージが 4 つある場合は、最初のステージをフェードアウトすると同時に、2 番目のステージでフェードインします。2 番目のステージが 100% の不透明度に達して最初のステージが 0% に達すると、2 番目のステージがフェードアウトしながら、3 番目のステージでフェードインします。完了したら、最後に第 3 ステージをフェードアウトし、最終バージョン 4 にフェードインします。このシナリオでは、各ステージに必要な時間の合計の 1/4 を要します。見た目は、実際のぼかし効果にとてもよく似ています。

Google のテストでは、ステージごとにぼかしの半径を指数関数的に大きくすることで、視覚的に最良の結果が得られました。例: 4 つのぼかしステージがある場合は、各ステージに filter: blur(2^n) を適用します(つまり、ステージ 0: 1px、ステージ 1: 2px、ステージ 2: 4px、ステージ 3: 8px)。will-change: transform を使用して、ぼかしを入れた各コピーをそれぞれのレイヤに強制する(プロモートと呼びます)と、これらの要素の不透明度の変更が非常に高速になります。理論的には、こうするとコストのかかるぼかし処理を先取りできます。結局、ロジックに欠陥があることがわかりました。このデモを実行すると、フレームレートはまだ 60 fps を下回っており、ぼかしは以前よりも悪くなっています。

GPU のビジー時間が長いトレースが表示されている DevTools。

DevTools をすぐに確認すると、GPU が非常にビジー状態で、各フレームが最大 90 ms に拡大していることがわかります。しかし、なぜこうなってしまったのでしょう。ぼかしの値はそのままで 不透明度だけを変えました問題は、ぼかし効果の性質にあります。前述のように、要素がプロモートされ、ぼかし加工されている場合、その効果は GPU によって適用されます。そのため、ぼかし値のアニメーション化は行っていませんが、テクスチャ自体にはぼかし加工が施されており、GPU でフレームごとにぼかし加工し直す必要があります。フレームレートが以前よりも悪くなった理由は、シンプルな実装と比較して GPU が実際に以前よりも多くの作業を行うようになったためです。ほとんどの場合、2 つのテクスチャが表示されて個別にぼかしが必要になるためです。

思い付いたことはきれいではありませんが、アニメーションは驚くほど高速になります。ぼかしを入れる要素をプロモートせずに、代わりに親ラッパーをプロモートします。要素がぼかしと昇格の両方される場合、その効果は GPU によって適用されます。これがデモの動作を遅らせた原因です。要素がぼかし加工されていて昇格されていない場合、代わりに最も近い親テクスチャにぼかしがラスタライズされます。この例では、昇格された親ラッパー要素です。ぼかしを入れた画像は親要素のテクスチャになり、今後のすべてのフレームに再利用できます。これがうまく機能するのは、ぼかした要素がアニメーション化されておらず、これらをキャッシュに保存することが実際に有益であることを確認しているからです。この手法を実装するデモをご覧ください。このアプローチについて Moto G4 はどう思う?次のように、

GPU のアイドル時間が大きいトレースが表示されている DevTools。

これで、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 をサポートしていません。そのようなブラウザについては、コンテンツを複製するだけであり、何も壊れることのない最善のものを目指しています。ShadyCSSShadow DOM ポリフィルを使用できますが、ライブラリには実装しませんでした。

Chrome のレンダリング パイプラインの経緯を経て、ブラウザ間で効率的にぼかしをアニメーション化する方法を見つけました。

まとめ

このような効果は控えめに使用する必要があります。DOM 要素をコピーして独自のレイヤに強制的に取り込むため、ローエンドのデバイスの限界を押し上げることが可能です。すべてのスタイルシートを各 ShadowRoot にコピーすることは、パフォーマンス上のリスクも潜む可能性があります。そのため、LightDOM のコピーの影響を受けないようにロジックとスタイルを調整するか、ShadowDOM の手法を使用するかを決定する必要があります。しかし、この手法に投資する価値がある場合もあります。GitHub リポジトリのコードとデモをご覧ください。ご不明な点がありましたら、Twitter でお問い合わせください。