加入模糊效果動畫

模糊處理是轉移使用者注意力的絕佳方式。讓部分視覺元素模糊處理,同時讓其他元素保持清晰,自然可引導使用者的注意力。使用者會忽略模糊處理的內容,專注於可閱讀的內容。舉例來說,您可以建立一個圖示清單,當游標懸停在圖示上時,就會顯示個別項目的詳細資料。在這段時間內,系統會模糊處理剩餘的選項,將使用者重新導向至新顯示的資訊。

TL;DR

模糊效果動畫效果非常緩慢,因此不建議使用。相反地,您可以預先計算一系列逐漸模糊的版本,並在這些版本之間進行交叉淡出。我的同事 Yi Gu 已編寫程式庫,為您處理所有事物!請參考我們的示範

不過,如果沒有任何過渡期就套用這項技術,使用者可能會感到相當不自然。為模糊效果製作動畫 (從未模糊到模糊) 似乎是合理的選擇,但如果您曾在網路上嘗試這麼做,您可能會發現動畫並非流暢,如這段示範影片所示,如果您沒有效能強大的裝置,我們可以做得更好嗎?

問題

標記會由 CPU 轉換為紋理。紋理會上傳至 GPU。GPU 會使用著色器將這些紋理繪製到框架緩衝區。模糊處理會在著色器中進行。

目前我們無法讓模糊效果動畫運作得更有效率。不過,我們可以找到看起來「還不錯」的解決方法,但從技術層面來說,這並不是動畫模糊處理。首先,讓我們瞭解為何動畫模糊效果會導致效能變慢。如要模糊處理網站上的元素,有兩種方法:CSS filter 屬性和 SVG 濾鏡。由於 CSS 篩選器的支援度和易用性不斷提升,因此通常會使用 CSS 篩選器。很抱歉,如果您必須支援 Internet Explorer,就只能使用 SVG 濾鏡,因為 IE 10 和 11 支援這些濾鏡,但不支援 CSS 濾鏡。好消息是,我們提供的模糊效果動畫解決方法適用於這兩種技巧。因此,我們來試著透過開發人員工具找出瓶頸。

如果您在開發人員工具中啟用「閃爍繪圖」,就不會看到任何閃爍效果。看來沒有重新繪製。從技術層面來說,這項說法是正確的,因為「重繪」是指 CPU 必須重繪已提升元素的紋理。只要元素同時經過提升和模糊處理,GPU 就會使用著色器套用模糊效果。

可擴充向量圖形篩選器和 CSS 篩選器都會使用卷積篩選器套用模糊效果。卷積濾鏡的運算成本相當高,因為每個輸出像素都必須考量多個輸入像素。圖片越大或模糊處理半徑越大,效果的成本就越高。

這就是問題所在,我們在每個影格都執行相當耗時的 GPU 作業,導致影格預算超過 16 毫秒,因此最終 FPS 遠低於 60fps。

深入瞭解

那麼,我們該如何讓這項功能順利運作?我們可以使用手法!我們並未為實際模糊處理值 (模糊處理半徑) 製作動畫,而是預先計算幾個模糊處理副本,讓模糊處理值以指數方式增加,然後使用 opacity 在這些副本之間進行交疊淡出效果。

交疊淡出/淡入效果是一系列重疊的半透明淡入/淡出效果。舉例來說,如果有四個模糊處理階段,我們會同時淡出第一個階段,並淡入第二個階段。當第二個階段的透明度達到 100%,而第一個階段的透明度達到 0% 時,我們會淡出第二個階段,同時淡入第三個階段。完成後,我們會淡出第三個階段,並淡入第四個也是最後一個版本。在這種情況下,每個階段都會佔用總計所需時間的 ¼。從視覺效果來看,這與實際的動畫模糊效果非常相似。

在實驗中,我們發現在每個階段以指數方式增加模糊半徑,可獲得最佳視覺效果。範例:如果有四個模糊處理階段,我們會將 filter: blur(2^n) 套用至每個階段,即階段 0:1 像素、階段 1:2 像素、階段 2:4 像素和階段 3:8 像素。如果我們使用 will-change: transform 將每個模糊處理過的副本強制置於各自的圖層 (稱為「提升」),則變更這些元素的透明度應該會非常快速。理論上,這可讓我們事先執行需要大量運算的模糊處理作業。結果發現邏輯有瑕疵。如果您執行這個示範,會發現幀率仍低於 60fps,且模糊程度實際上比之前更嚴重

開發人員工具顯示的追蹤記錄,其中 GPU 有長時間的忙碌時間。

快速查看 DevTools 後,您會發現 GPU 仍非常繁忙,且每個影格都會延長至約 90 毫秒。但為什麼呢?我們不再變更模糊值,只會變更不透明度,那麼發生了什麼事?問題再次出現在模糊效果的本質:如先前所述,如果元素同時經過提升和模糊處理,GPU 就會套用效果。因此,即使我們不再為模糊值設定動畫,但材質本身仍未模糊處理,因此需要由 GPU 在每個影格中重新模糊處理。幀率比之前更糟的原因,是因為與簡單實作方式相比,GPU 實際上需要處理更多工作,因為大多數情況下,兩個需要獨立模糊處理的紋理都會顯示出來。

我們採用的做法雖然不太美觀,但能讓動畫以極快的速度播放。我們將回到「不」宣傳要模糊處理的元素,而是宣傳父項包裝函式。如果元素同時經過模糊處理和提升,則會由 GPU 套用效果。這就是導致示範速度變慢的原因。如果元素經過模糊處理但未經過提升,則模糊處理會轉換為最近的父項紋理。在本例中,就是升級的父項包裝元素。模糊處理過的圖片現在是父元素的紋理,可用於日後的所有影格。這項做法之所以有效,是因為我們知道模糊處理的元素並非動畫,因此快取這些元素其實有益。以下是實作此技巧的示範。想知道 Moto G4 對這個做法有何看法?提前爆雷:它認為自己很棒:

開發人員工具顯示 GPU 閒置時間較多的追蹤記錄。

如今,我們在 GPU 上有充足的空間,而且可以流暢地以 60 張/秒的速度運作。我們成功了!

正式版

在示範中,我們複製了 DOM 結構多次,以便將內容副本模糊處理成不同強度。您可能會想知道這項功能在實際工作環境中如何運作,因為這可能會對作者的 CSS 樣式或甚至 JavaScript 產生一些非預期的副作用。你說得沒錯。進入 Shadow DOM!

雖然大多數人認為 Shadow DOM 是將「內部」元素附加至自訂元素的一種方式,但它也是隔離和效能的基本元素!JavaScript 和 CSS 無法穿透 Shadow DOM 邊界,因此我們可以複製內容,而不會干擾開發人員的樣式或應用程式邏輯。我們已經為每個副本建立了 <div> 元素,以便將其轉為點陣圖,現在則會使用這些 <div> 做為陰影主機。我們使用 attachShadow({mode: 'closed'}) 建立 ShadowRoot,並將內容的副本附加至 ShadowRoot,而不是 <div> 本身。我們必須確保將所有樣式表單複製到 ShadowRoot,以確保複本的樣式與原始樣式相同。

部分瀏覽器不支援 Shadow DOM v1,因此我們會改為複製內容,並希望不會發生任何錯誤。我們可以使用 Shadow DOM polyfill 搭配 ShadyCSS,但我們並未在程式庫中實作這項功能。

就是這樣。在瞭解 Chrome 轉譯管道後,我們發現如何在各瀏覽器之間有效地製作模糊效果動畫!

結論

這類效果不應隨意使用。由於我們會複製 DOM 元素並強制將其置於各自的圖層,因此可以突破低階裝置的限制。將「所有」樣式表複製到每個 ShadowRoot 也是潛在的效能風險,因此您應該決定是否要調整邏輯和樣式,以免受到 LightDOM 中副本的影響,或是使用我們的 ShadowDOM 技巧。但有時我們的技術可能會是值得投資的項目。請查看 GitHub 存放區中的程式碼,以及示範,如有任何問題,歡迎在 Twitter 上與我聯絡!