最近看到一篇文章,介绍了一种针对模糊效果的动画。
目前实现模糊效果有两种方式:CSS filter 属性和 SVG filters,后者的支持度更高。 在这个 Demo中,使用了 CSS filter,定义了针对模糊半径的一个动画。
@keyframes b1-anim {
0% { filter: blur(0px); }
100% { filter: blur(16px); }
}
在高性能设备上能够流畅运行,而在手持设备中会有卡顿。
CSS filter 的问题
从下面的渲染流水线可以看出,blur 和 opacity
transform
一样,不会触发浏览器的重绘。那么为什么会存在卡顿呢?
原来问题出在 blur 使用的算法上。无论是 CSS 还是 SVG,计算模糊都是使用 convolution filters。这种算法在图片像素增多和模糊半径增大时,开销会显著增大。
变通方法
既然实时变化模糊半径计算开销大,我们可以将整个模糊变化分成几个阶段,预先使用 filter
定义这几个阶段的模糊半径,然后使用 opacity
控制每个阶段的显示。
比如我们分成 4 个阶段,注意这里需要 HTML 冗余:
<div>
<img id="b1"/>
<img id="b2"/>
<img id="b3"/>
<img id="b4"/>
</div>
定义每个阶段固定的模糊半径避免实时变化,还可以结合使用 will-change: transform
:
@keyframes b2-anim {
0% { opacity: 0; }
33% { opacity: 1; }
66% { opacity: 1; }
67% { opacity: 0; }
100% { opacity: 0; }
}
@keyframes b1-anim {
0% { opacity: 1; }
33% { opacity: 1; }
34% { opacity: 0; }
100% { opacity: 0; }
}
#b1 {
will-change: transform
animation: b1-anim 1s infinite alternate linear;
}
#b2 {
will-change: transform
-webkit-filter: blur(4px);
animation: b2-anim 1s infinite alternate linear;
}
那么实际效果如何呢?在我自己的机器上运行并没有原文中那么大的提升。不过虽然最原始的方式也能保证 16ms,但是 GPU 的处理时间在采用这种方式之后,从每帧 7ms -> 3ms。
进一步优化
不难发现,主要需要优化的是冗余的 HTML,增加了之后会不会影响到原有页面中的样式规则呢? Web Components 中的 Shadow DOM 可以解决这个问题。
在上面的例子中,<div id="container">
可以作为影子根节点的宿主节点。这样有效地划分了 CSS 和 JS 的边界。