引子
某日移动端有一需求:要求一 App Logo 有一层外阴影闪动效果,实现起来倒也不复杂。简单粗暴直接在 keyframes
中定义 box-shadow
动画即可交差,最终代码如下:
.box {
margin: 100px auto;
width: 200px;
height: 200px;
border-radius: 4px;
animation: boxAnimation 1s infinite linear alternate-reverse;
}
@keyframes boxAnimation {
form {
box-shadow: 0 0 0px rgba(0, 0, 0, .4);
}
to {
box-shadow: 0 0 50px rgba(0, 0, 0, .4);
}
}
动画在 PC 端运行时我这写轮眼是看不出任何卡顿的,一旦在模拟器或者移动端上运行情况就不那么乐观了,出现了明显可感知的掉帧。
Why?
首先我们会归咎于移动端设备的性能落于 PC。是啊,红米 Note 4 的机能与 MacBook Pro 2016 顶配之间的性能差距非常巨大。但在同一移动设备下其他的动画却很流畅的,比如 transform
,那为什么偏偏 box-shadow
就会导致性能问题呢?而且 box-shadow
也是个惯犯了,之前就爆出 box-shadow
在页面滚动时会导致性能问题,既然有前科这就好办了。经过查阅资料得知:
不同样式在消耗性能方面是不同的,有些效果(如经常被人提起的
box-shadow
)从渲染角度来讲十分耗性能,原因就是与其他样式相比,它们的绘制代码执行时间过长。也就是说,如果一个耗性能严重的样式经常需要重绘,那么你就会遇到性能问题。
via Scrolling Performance
而 keyframes
中定义的动画是循环改变 box-shadow
,导致浏览器会一直重绘耗性能严重的样式,进而产生性能问题。
解决问题嘛,可不可以先解决提出问题的 CSS 属性 —— box-shadow
呢?我觉得删除是不可能删除的,这辈子都不可能删除的,box-shadow
长得又好看,用起来还简单,我超喜欢用的!那么优化思路就剩下如何阻止浏览器一直绘制 box-shadow
。
How
至于怎么做 CSS-TRICKS 直接给出了解决方案:为伪元素设置 box-shadow
并对其不透明度设置动画:
How do you animate the box-shadow property in CSS without causing re-paints on every frame, and heavily impacting the performance of your page? Short answer: you don’t. Animating a change of box-shadow will hurt performance.
There’s an easy way of mimicking the same effect, however, with minimal re-paints, that should let your animations run at a solid 60 FPS: animate the opacity of a pseudo element.
trick by Tobias Ahlin:
根据其建议,我们将代码修改如下,果然模拟器或移动端设备上动画不卡了!打开 Chrome DevTools 中帧率观察器,帧率也一直是以 60fps 的一条直线,而不是之前的略有波动。
.box {
margin: 100px auto;
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: 0 0 50px 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(错误地)淡化其它的元素。
via High Performance Animations
原来 opacity
真的不一样啊!因为在 Blink 和 WebKit 内核的浏览器中,对于在 CSS transition
或者 animation
中有 opacity
改变的元素,浏览器改将会为其创建一个图层,然后交给其小秘 GPU 去分担处理。同样能享受这些待遇的动画属性如下:
现代浏览器在完成以下四种属性的动画时,消耗成本较低:position(位置)1, scale(比例缩放), rotation(旋转) 和 opacity(透明度)。
via High Performance Animations
上文可知 scale
也是不错的备选方案,但我为什么没有选?如果对伪元素做 scale
动画会导致动画效果跟 box-shadow
有差异。既然创建一个交由 GPU 渲染的图层这么厉害,那么我们可不可以强制创建一个?那就是 前端交互动画优化 中提到的认为产生硬件加速了。
最近的文章都离不开浏览器的回流与重绘,希望自己接下来能整理下相关的知识点(无意立了个 flag,假装没看见好啦)。
参考
- How to animate "box-shadow" with silky smooth performance
- High Performance Animations
- Scrolling Performance
- CSS Paint Times and Page Render Weight
-
这里指代是
translate
或者translate3d
↩