模糊效果是轉移使用者注意力的絕佳方式。模糊處理部分視覺元素,同時讓其他元素保持清晰,自然能引導使用者將注意力放在特定位置。使用者會忽略模糊處理的內容,專注於可閱讀的內容。舉例來說,如果使用者將游標懸停在圖示清單上,系統就會顯示個別項目的詳細資料。在這段期間,系統可能會模糊處理其餘選項,將使用者重新導向新顯示的資訊。
TL;DR
動畫模糊效果並非理想選擇,因為速度很慢。而是預先計算一系列越來越模糊的版本,並在這些版本之間進行交叉淡化。我的同事 Yi Gu 編寫了程式庫,可為您處理所有事項!歡迎參考我們的示範。
不過,如果沒有過渡期就直接套用這項技術,可能會造成相當大的衝擊。將模糊效果設為動畫 (從未模糊到模糊的過渡效果) 似乎是合理的選擇,但如果您曾在網路上嘗試這麼做,可能會發現動畫並不流暢,如這個範例所示 (如果您的電腦效能不佳)。我們是否可以做得更好?
問題

目前我們無法有效率地製作模糊動畫。不過,我們可以找到看起來夠好的替代方案,但嚴格來說,這並非動畫模糊效果。首先,請先瞭解動畫模糊效果緩慢的原因。如要在網頁上模糊處理元素,有兩種方法:使用 CSS filter
屬性和 SVG 濾鏡。由於支援度提高且易於使用,CSS 篩選器通常會派上用場。很抱歉,如果您必須支援 Internet Explorer,就只能使用 SVG 濾鏡,因為 IE 10 和 11 支援 SVG 濾鏡,但不支援 CSS 濾鏡。好消息是,我們提供的模糊動畫解決方法適用於這兩種技術。因此,我們不妨查看開發人員工具,找出瓶頸。
如果您在開發人員工具中啟用「Paint Flashing」,就不會看到任何閃爍。It looks like no repaints are happening. 從技術上來說,這確實是「重新繪製」,因為「重新繪製」是指 CPU 必須重新繪製升級元素的紋理。如果元素同時獲得升級和模糊處理,GPU 會使用著色器套用模糊效果。
可擴充向量圖形篩選器和 CSS 篩選器都會使用迴旋篩選器套用模糊效果。由於每個輸出像素都必須考量一定數量的輸入像素,因此迴旋濾鏡的成本相當高。圖片越大或模糊半徑越大,效果的成本就越高。
問題就在這裡,我們在每個影格都執行相當耗費資源的 GPU 作業,導致影格預算 (16 毫秒) 不敷使用,因此最終遠低於 60 fps。
深入瞭解
那麼該怎麼做才能順利完成這項程序?我們可以運用手法!我們不會為實際模糊值 (模糊的半徑) 製作動畫,而是預先計算幾個模糊副本,模糊值會以指數方式增加,然後使用 opacity
在這些副本之間進行交叉淡出。
淡入淡出效果是透過一系列重疊的不透明度淡入和淡出效果呈現。舉例來說,如果我們有四個模糊階段,就會在淡入第二階段的同時淡出第一階段。第二階段達到 100% 不透明度,而第一階段達到 0% 時,我們會淡出第二階段,同時淡入第三階段。完成後,我們最終會淡出第三階段,並淡入第四個最終版本。在這種情況下,每個階段會佔總目標時間的 ¼。在視覺上,這與真實的動畫模糊效果非常相似。
在實驗中,我們發現每個階段的模糊半徑呈指數成長,可產生最佳的視覺效果。舉例來說,如果我們有四個模糊階段,我們會對每個階段套用 filter: blur(2^n)
,也就是階段 0:1 像素、階段 1:2 像素、階段 2:4 像素,以及階段 3:8 像素。如果我們使用 will-change: transform
將每個模糊副本強制放到各自的圖層上 (稱為「升級」),變更這些元素的透明度應該會非常快速。理論上,這可讓我們預先完成模糊處理這項耗費資源的工作。結果發現邏輯有瑕疵。如果您執行這個範例,會發現影格速率仍低於 60 fps,而且模糊程度實際上比之前更嚴重。

快速查看開發人員工具後,會發現 GPU 仍非常忙碌,且每個影格都會延遲約 90 毫秒。但為什麼?我們不再變更模糊值,只變更不透明度,所以發生了什麼事?問題再次出在模糊效果的性質:如先前所述,如果元素同時升級和模糊,效果會由 GPU 套用。因此,即使我們不再為模糊值製作動畫,紋理本身仍未模糊,且 GPU 必須在每個影格重新模糊紋理。幀率比以往更差的原因,在於與單純的實作方式相比,GPU 實際上需要處理更多工作,因為大部分時間都有兩個紋理可見,需要分別模糊處理。
我們想出的方法並不美觀,但可讓動畫快速執行。我們不會再宣傳要模糊處理的元素,而是宣傳父項包裝函式。如果元素同時模糊處理和升級,效果會由 GPU 套用。這就是導致示範緩慢的原因。如果元素已模糊處理但未升級,模糊效果會改為點陣化至最接近的父項紋理。在本例中,這是宣傳的父項包裝函式元素。模糊處理的圖片現在是父項元素的紋理,可供日後所有影格重複使用。只有在我們知道模糊處理的元素不會有動畫效果,且快取這些元素確實有益時,這項做法才有效。以下是實作這項技術的示範。不知道 Moto G4 對這種做法有什麼看法?提前爆雷:它認為自己很棒:

現在 GPU 有充足的空間,而且能以流暢的 60 FPS 執行。我們成功了!
正式版
在我們的示範中,我們多次複製 DOM 結構,以取得內容副本,並以不同強度模糊處理。您可能會想知道這在正式環境中會如何運作,因為這可能會對作者的 CSS 樣式,甚至是 JavaScript 產生一些非預期的副作用。你說得沒錯。輸入 Shadow DOM!
大多數人認為 Shadow DOM 是將「內部」元素附加至 Custom Elements 的方式,但它也是隔離和效能基本型別!JavaScript 和 CSS 無法穿透 Shadow DOM 邊界,因此我們可以在不干擾開發人員的樣式或應用程式邏輯的情況下,複製內容。我們已為每個副本建立 <div>
元素,以便光柵化至該副本,現在則將這些 <div>
做為陰影主機。我們會使用 attachShadow({mode: 'closed'})
建立 ShadowRoot
,並將內容副本附加至 ShadowRoot
,而不是 <div>
本身。我們也必須確保將所有樣式表複製到 ShadowRoot
中,以確保副本的樣式與原始版本相同。
部分瀏覽器不支援 Shadow DOM v1,對於這類瀏覽器,我們會改為複製內容,並盡量確保不會發生任何問題。我們可以搭配 ShadyCSS 使用 Shadow DOM polyfill,但我們並未在程式庫中實作這項功能。
就這樣,在我們深入瞭解 Chrome 的算繪管道後,終於知道如何在各個瀏覽器中有效製作模糊動畫!
結論
請勿輕易使用這類效果。由於我們會複製 DOM 元素並強制將其放在自己的圖層上,因此可以突破低階裝置的限制。將所有樣式表複製到每個 ShadowRoot
中,也可能造成效能風險,因此您應決定是否要調整邏輯和樣式,以免受到 LightDOM
中的副本影響,或是使用我們的 ShadowDOM
技術。但有時我們的技術可能值得投資。請查看 GitHub 存放區中的程式碼,以及示範。如有任何問題,歡迎在 Twitter 上與我聯絡!