例子
某日移動端有一需求:要求一 App Logo 有一層外陰影閃動效果,實現起來倒也不複雜。簡單粗暴直接在keyframes中定義box-shadow動畫即可交差,最終代碼如下:
.box {
margin:100pxauto; width:200px;
height:200px;
border-radius:4px;
animation: boxAnimation 1s infinite linear alternate-reverse;
}
@keyframes boxAnimation {
form {
box-shadow:000px rgba(0,0,0,.4);
}
to {
box-shadow:0050px rgba(0,0,0,.4);
}
}
動畫在 PC 端運行時我這寫輪眼是看不出任何卡頓的,一旦在模擬器或者移動端上運行情況就不那麼樂觀了,出現了明顯可感知的掉幀。
Why?
首先我們會歸咎於移動端設備的性能落於 PC。是啊,紅米 Note 4 的機能與 MacBook Pro 2016 頂配之間的性能差距非常巨大。但在同一行動裝置下其他的動畫卻很流暢的,比如transform,那為什麼偏偏box-shadow就會導致性能問題呢?而且box-shadow也是個慣犯了,之前就爆出box-shadow在頁面滾動時會導致性能問題,既然有前科這就好辦了。經過查閱資料得知:
不同樣式在消耗性能方面是不同的,有些效果(如經常被人提起的box-shadow)從渲染角度來講十分耗性能,原因就是與其他樣式相比,它們的繪製代碼執行時間過長。也就是說,如果一個耗性能嚴重的樣式經常需要重繪,那麼你就會遇到性能問題。
而keyframes中定義的動畫是循環改變box-shadow,導致瀏覽器會一直重繪耗性能嚴重的樣式,進而產生性能問題。
解決問題嘛,可不可以先解決提出問題的 CSS 屬性 ——box-shadow呢?我覺得刪除是不可能刪除的,這輩子都不可能刪除的,box-shadow
長得又好看,用起來還簡單,我超喜歡用的!那麼優化思路就剩下如何阻止瀏覽器一直繪製box-shadow。
How
至於怎麼做 CSS-TRICKS 直接給出了解決方案:為偽元素設置box-shadow並對其不透明度設置動畫:
根據其建議,我們將代碼修改如下,果然模擬器或移動端設備上動畫不卡了!打開 Chrome DevTools 中幀率觀察器,幀率也一直是以 60fps 的一條直線,而不是之前的略有波動。
.box {
margin:100pxauto;
width:200px;
height:200px;
border-radius:4px;
}
.box::after {
content:'';// 防止遮蔽父層
position: absolute;
z-index:-1;
display: block;
width: inherit;
height: inherit;
border-radius: inherit;
box-shadow:0050px rgba(0,0,0,.4);
will-change: opacity;
opacity:0;
animation: fadeIn 1s infinite linear alternate-reverse;
}
@keyframes fadeIn {
form {
opacity:0;
} to {
opacity:1;
}
}
opacity一邊樂呵大唱「我們不一樣」!那問題來了,為何opacity不會引發瀏覽器的重繪?
有一個 CSS 屬性,你可能認為它會引起重繪,但有時候並不會。就是:opacity。當GPU在合成元素的紋理結構的時候,會以一個較低的 alpha 值去處理opacity的改變。它的條件是,元素必須是圖層中唯一的一個元素。如果它和其它的元素組合在一起,那麼對opacity的改變也會讓 GPU(錯誤地)淡化其它的元素。
原來opacity真的不一樣啊!因為在 Blink 和 WebKit 內核的瀏覽器中,對於在 CSStransition或者animation中有opacity改變的元素,瀏覽器改將會為其創建一個圖層,然後交給其小秘 GPU 去分擔處理。同樣能享受這些待遇的動畫屬性如下:
現代瀏覽器在完成以下四種屬性的動畫時,消耗成本較低:position(位置)<sup id=「fnref1」>1</sup>, scale(比例縮放), rotation(旋轉) 和 opacity(透明度)。
上文可知scale也是不錯的備選方案,但我為什麼沒有選?如果對偽元素做scale動畫會導致動畫效果跟box-shadow有差異。既然創建一個交由 GPU 渲染的圖層這麼厲害,那麼我們可不可以強制創建一個?那就是 前端交互動畫優化 中提到的認為產生硬體加速了。