实用的 CSS — 动画性能对比

前言

在现代浏览器中,渲染页面所要负责的线程主要有两个:主线程和排版线程。

主线程

  • 运行 JS
  • 计算 HTML 元素的 CSS 样式
  • 布局页面
  • 把页面元素绘制成一个或多个位图
  • 把这些位图移交给排版线程

在浏览器开始渲染页面,或者长时间执行某个 JS 时,主线程会一直在忙碌状态,此时对于用户的任何输入或是操作都不会有所响应。

排版线程

  • 通过 GPU 渲染位图,并显示在屏幕上
  • 向主线程请求更新位图的可见部分或即将可见的部分
  • 判断出当前页面处于可见的部分
  • 判断出即将通过页面滚动而可见的部分
  • 随着用户滚动页面来移动这些部分

排版线程对于用户的操作保持快速的响应,普遍的效率时每秒 60 帧的速度去刷新显示。

Transtion

下面我们在网页中实现一个元素的高度变化的动画,鼠标悬浮在元素上动画启动,直至完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<style>
#foo {
height: 100px;
width: 100px;
background: red;
transition: height 1s linear;
}
#foo:hover {
height: 200px;
}
</style>
<body>
<div id="foo"></div>
</body>

通过对上述代码的观察,让我们来了解一下浏览器的两个线程是如何协同工作的:

图中橘黄色部分代表操作相对较慢,消耗较大;蓝色部分代表操作相对较快,消耗较小

animations-performance-transition

从上图我们可以看到,浏览器的两个线程在来回地切换工作,而且橘黄色出现次数较多,这意味着浏览器需要处理相当多的工作。

对于浏览器而言,由于元素的高度一直在变化,因此这个动画的每一帧中,都需要重新布局 ——> 绘制页面 ——> 将新的位图加载到 GPU 中 ——> 显示。而其中加载到 GPU 是一个相对缓慢的操作。

同时我们也在通过浏览器去查看元素动画的过程,其实是由略微卡顿的现象的。

Transform

经过上面的实验,我们对 transition 属性有了比较好的了解;同时我们对上述动画性能也有一个了解。接着我需要在网页中实现一个元素的大小变化动画,鼠标悬浮在元素上动画启动,直至完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<style>
#bar {
height: 100px;
width: 100px;
background: red;
transition: transform 1s linear;
}
#bar:hover {
transform: scale(2);
}
</style>
<body>
<div id="bar"></div>
</body>

完成上述实验,再让我们来看看两个线程工作的过程:

animations-performance-transform

由此我们可以看到,两个线程来回切换的情况并不多,橘黄色部分出现的次数也较少,蓝色部分居绝大部分,这意味着这个动画效果相较于上面的要流畅很多。

在定义中,transform 是不会使浏览器产生重新排版的,因此 transform 不会影响原本的布局,以及周围的元素。它会将定义的元素作为一个整体进行缩放、移动或旋转等。

基于 transform 这类的特性,浏览器在渲染页面时可以节省很多不必要的开支,例如重新布局和将位图传给 GPU 等工作,这样就使得动画更有效率。

总结

当页面需要位移动画时,我们有两种方案:使用 position 或是 transalte,而这两种是符合上述情况的。其中 position 的位移方案与第一个符合,在动画执行过程中会使浏览器重新渲染;另一外 transalte 则与第二个符合,在执行动画时不会发生重新渲染。因此,在需要写动画时,我们需要选择合适的方案,最好是选择 scale()rotate()transalte() 等,因为他们具有更好的性能。

参考

W3C: CSS Transforms
W3C: CSS Transitions
css-animations-and-transitions-performance

如果觉得有用,您的支持将鼓励我继续创作!