你也许早已在项目中使用上了Animations 或 Transitions(如果还没有,可以阅读CSS-Trick’s almanac 关于animations或transitions的相关文章)。你会发现你的一些运动表现流畅,而另一些却不尽如人意,你想知道原因?
本文将阐述浏览器是怎样处理CSS animations 与 transitions。以期在编写代码之前,你可以通过直觉判断某一运动能否运行良好。通过这种直觉,你将能过做出浏览器亲和且用户体验流畅的设计决策。
深入浏览器内部
让我深入浏览器内部一探究竟。只有理解了它的工作用力,我们才能做的更好。
现代浏览器有两个重要的执行线程。两个线程共同协作来渲染web页面。他们是:
- 主线程(The main thread)
- 排版线程(The compositor thread)
一般来说,主线程负责:
- 运行JS代码
- 计算HTML元素的CSS样式
- 布局页面
- 将元素绘制成一副或多幅位图
- 将位图传给排版线程
排版线程则负责:
- 通过GPU,将位图绘制到屏幕上
- 对可见或即将可见的区域,询问主线程是否进行位图更新。
- 计算页面的可见区域
- 当滚动屏幕时,计算出即将可见的区域
- 当滚动时移动页面区域
主线程花费大量的时间忙于执行JS代码与绘制大型元素。主线程正在处理任务时,他将无法响应用户的输入(注:这里是广义的输入,泛指交互)。
另一方面,排版线程保持着对用户的输入的响应。当页面改变时,排版线程会进行每秒60次的重绘。甚至在页面还不完整时。
例如,当用户滚动页面时,排版线程会询问主线程是否为新的可见区域更新位图。然而,当主线程的反馈不那么迅速时,排版线程并不会等待。他将对已有反馈的页面进行绘制并使用空白代替未反馈的部分。
GPU
上文提到排版线程通过GPU将位图绘制到屏幕上。让我们聊一聊GPU。
GPU是现今大多数手机,平板,电脑的组成部分。它是一个相当专门化的部件,这意味着它只专注处理一些事务。
GPUs 能够快速处理:
- 把图形绘制到屏幕上
- 重复绘制位图
- 在不同的区域绘制相同位图或将位图旋转,缩放。
transition: height
现在我们对浏览网页时,硬件与软件的大致行为。让我们来看看究竟浏览器的两个线程是如何协同运作来完成一个CSS动画的。
假设我们使用如下代码,将一个元素从100px变为200px:
- div {
- height: 100px;
- transition: height 1s linear;
- }
- div:hover {
- height: 200px;
- }
两个线程将会按照下图示意的操作执行。橙色框中的操作会消耗大量时间,而蓝色框的操作运行迅速。
如图,这一过程中有大量的黄色框,这意味着浏览器并不能流畅运行。过渡动画将会卡顿。
在每一帧的过渡动画,浏览器都将执行布局,绘制以及向GPC内存更新新的位图的操作。如我们所知,向GPU内存加载位图是一个相当缓慢的操作。
浏览器每一帧运行不流畅的原因在于元素的内容在持续变化。变化元素的高度意味着其子元素的形状也跟着改变,因此浏览器要进行布局。布局后,主线程要为元素从新生成位图。
transition: transform
因此,改变高度是一种高代价的过渡动画。那么有什么的代价比较低廉?
假设我们将一个元素的尺寸从一半还原会正常尺寸。同时,假设使用CSS transform 属性去缩放,采用过渡完成动画,代码如下:
- div {
- transform: scale(0.5);
- transition: transform 1s linear;
- }
- div:hover {
- transform: scale(1.0);
- }
让我们看看这个过程的示意图:
这次橙色框明显减少,这意味着动画更加流畅。元素transform 变化与高度变化的动画究竟区别在哪?
通过定义,CSS transform 属性并没有改变元素与相邻元素的布局,仅仅只影响了作为一个整体的元素自身(缩放,旋转整个元素或移动整个元素)。
这对于浏览器来说是个好消息。浏览器仅需要产生元素的位图,并在动画开始时向GPU更新位图。之后,浏览器不用再做更多的布局,绘制和位图更新操作。取而代之的是,仰仗GPU专业的能力在不同的区域绘制相同的位图,或使之旋转,缩放。
设计决策
如此,是否意味着不应进行元素高度的动画?不,有时这仰赖你的设计,运动同样能运行的足够快。也许你的元素是独立的,并不会造成其它部分的重排。也许你的元素只是简单的重绘,浏览器可以快速地执行。也许你的元素很小,浏览器只要向GPU更新一个小的位图。
当然,如果你的动画使用像CSS transform一样的“廉价”属性替代像CSS height的更“昂贵”的属性,并且这不影响你的设计理念,那就这样做吧。例如,当你的设计需要一个按钮在点击时显示菜单,你需要尝试元素的transform属性来显示菜单而不是通过使用top或height属性来实现相同或相似的效果。
下面列出一些能实现快速动画的CSS属性:
- CSS transform
- CSS opacity
- CSS filter (取决于filter的复杂程度与浏览器性能)
这个列表现在看来可能很有限,但随着浏览器的进步,越来越多的属性会运动地更快。同时,只是用这个列表上的属性,你也会惊叹于仅仅通过使用这些属性的组合就能创造出大量丰富的效果。