CSS动画与JavaScript动画:打破迷思

Avatar of Jack Doyle
Jack Doyle 发表

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200美元免费额度!

以下是 Jack Doyle 的客座文章,他是 GreenSock动画平台 (GSAP) 的作者。Jack 在浏览器动画方面做了大量工作,并发现普遍认为“CSS更快”的说法并不正确。而且,情况比这更复杂。我让他来解释一下。

曾经有一段时间,大多数开发者使用 jQuery 来为浏览器中的元素添加动画。淡入淡出、展开收缩; 都是一些简单的东西。随着交互式项目的复杂度不断提升,移动设备的兴起,性能变得越来越重要。Flash逐渐淡出,才华横溢的动画师推动HTML5做它从未做过的事情。他们需要更好的工具来实现复杂的动画序列和顶级的性能。jQuery的设计根本无法满足这些需求。浏览器也逐渐成熟,并开始提供解决方案。

最广受赞誉的解决方案是 CSS动画(以及 过渡)。作为行业多年的宠儿,CSS动画在各种会议上被反复提及,诸如“硬件加速”和“移动友好”之类的短语也让听众们心驰神往。基于JavaScript的动画则被视为过时和“肮脏”的东西。但事实真是如此吗?

作为一名对动画和性能充满热情(实际上近乎痴迷)的人,我迫不及待地跳上了CSS的战车。然而,不久之后,我便开始发现了一些没人谈论的重大问题。我震惊了。

本文旨在提高大家对基于CSS的动画的一些重要缺陷的认识,以便您可以避免我遇到的麻烦,并更明智地决定何时使用JS,何时使用CSS进行动画。

缺乏独立的缩放/旋转/位置控制

对元素的缩放、旋转和位置进行动画处理非常常见。在CSS中,它们都被塞进了一个“transform”属性中,这使得无法真正独立地对单个元素进行动画。例如,如果您想独立地对“旋转”和“缩放”进行动画,并使用不同的时间和缓动效果,该怎么办?也许某个元素在不断脉动(缩放振荡),并且您希望在鼠标悬停时旋转它。这只有通过JavaScript才能实现。

查看 GreenSock (@GreenSock) 在 CodePen 上的示例 独立变换

在我看来,这是CSS的一个明显弱点,但如果您只做一些简单的动画,并且在任何给定时间都对整个变换状态进行动画处理,那么这将不是问题。

性能

网络上大多数比较都是将CSS动画与 jQuery 进行对比,因为jQuery非常普遍(就好像“JavaScript”和“jQuery”是同义词一样),但众所周知,jQuery在动画性能方面相当缓慢。更新的 GSAP 也是基于JavaScript的,但它的速度实际上比jQuery快20倍。因此,JavaScript动画声誉不佳的部分原因就是我所说的“jQuery因素”。

使用CSS进行动画的最常被提及的原因是**“硬件加速”**。听起来很不错,对吧?让我们把它分成两部分。

GPU参与

GPU 针对诸如移动像素和应用变换矩阵以及不透明度等任务进行了高度优化,因此现代浏览器会尝试将这些任务从CPU卸载到GPU。关键是将动画元素隔离到它们自己的GPU层上,因为一旦创建了一个层(只要其原生像素没有改变),GPU就可以轻松地移动这些像素并将其组合在一起。它可以保存像素块(作为层),然后只需说“将该块向右移动10像素,向下移动5像素”(或任何值),而不是每秒计算每个像素60次。

旁注:不要为每个元素都创建自己的层,因为GPU的显存是有限的。如果显存耗尽,性能会急剧下降。

在CSS中声明动画允许浏览器确定哪些元素应该获得GPU层,并相应地将它们分配。非常棒。

**但是您是否知道JavaScript也可以做到这一点?**使用具有3D特征的变换(如translate3d()matrix3d())会触发浏览器为该元素创建一个GPU层。因此,GPU加速**不仅仅**适用于CSS动画——JavaScript动画也可以从中受益!

还要注意,并非所有CSS属性都能在CSS动画中获得GPU加速。实际上,大多数都不能。变换(缩放、旋转、平移和倾斜)和不透明度是主要受益者。因此,不要假设只要使用CSS进行动画,所有内容都会自动获得GPU加速。事实并非如此。

将计算卸载到不同的线程

“硬件加速”的另一部分与能够使用不同的CPU线程进行动画相关的计算有关。同样,这在理论上听起来很棒,但并非没有成本,而且开发人员往往高估了其优势。

首先,只有不影响文档流的属性才能真正被分配到不同的线程。因此,再次强调,变换和不透明度是主要受益者。当您分离其他线程时,管理此过程会涉及一些开销。由于图形渲染和文档布局在大多数动画中(**远远地**)消耗了最多的处理资源(**而非**计算属性补间值的中间值),因此使用单独的线程进行插值的优势很小。例如,如果在特定动画期间**98%**的工作是图形渲染和文档布局,而**2%**是计算新的位置/旋转/不透明度/任何值,即使您将这些值计算速度提高了**10倍**,您也只会看到大约**1%**的整体速度提升。

性能比较

下面的压力测试创建了特定数量的图像元素(点),并将它们从中心动画到边缘周围的随机位置,并使用随机延迟,从而创建星场效果。增加点的数量,并查看 jQueryGSAPZepto 的对比情况。由于Zepto将其所有动画都使用CSS过渡,因此它应该表现最好,对吧?

查看 GreenSock (@GreenSock) 在 CodePen 上的示例 速度测试:GSAP与Zepto (CSS过渡)与jQuery

结果证实了网络上广泛报道的内容——CSS动画明显快于jQuery。但是,在我测试的大多数设备和浏览器上,**基于JavaScript的GSAP的性能甚至优于CSS动画**(在某些情况下优势明显,例如在Microsoft Surface RT上,GSAP的速度可能至少是Zepto创建的CSS过渡的5倍,而在iPad 3 iOS7上,当使用GSAP而不是CSS过渡进行动画处理时,变换的速度明显更快)。

动画属性 JavaScript更佳 CSS更佳
top、left、width、height Windows Surface RT、iPhone 5s (iOS7)、iPad 3 (iOS 6)、iPad 3 (iOS7)、Samsung Galaxy Tab 2、Chrome、Firefox、Safari、Opera、Kindle Fire HD、IE11 (无)
变换 (translate/scale) Windows Surface RT、iPhone 5s (iOS7)、iPad 3 (iOS7)、Samsung Galaxy Tab 2、Firefox、Opera、IE11 iPad 3 (iOS6)、Safari、Chrome
究竟“好多少”?测试的原始版本有一个每秒帧数计数器,用于提供可量化的结果,但很快发现,在浏览器(尤其是使用CSS动画时)之间没有真正准确的方法来测量FPS,并且某些浏览器报告了误导性的数字,所以我将其删除了。但是,您可以轻松地衡量相对性能,方法是增加点的数量,在引擎之间切换,并观察性能如何(流畅的运动、稳定的时间和点的分散等)。毕竟,目标是让动画看起来很棒

值得注意的事项

  • 在对top/left/width/height(影响文档流的属性)进行动画处理时,JavaScript在所有情况下都更快(GSAP,而非jQuery)。
  • 一些设备似乎针对变换进行了高度优化,而另一些设备则更好地处理了top/left/width/height动画。最值得注意的是,较旧的iOS6在使用CSS动画变换时效果更好,但较新的iOS7却发生了变化,现在它们的速度明显变慢了(我 在这里写了一篇博客)。
  • CSS动画在浏览器计算层并将数据上传到GPU时,初始动画启动会有明显的延迟。这也适用于基于JavaScript的3D变换,因此“GPU加速”并非没有成本。
  • 在高负载下,CSS过渡更容易出现条带/环状效果(这似乎是一个同步/调度问题,可能是由于它们在不同的线程中进行管理)。
  • 在某些浏览器(如Chrome)中,当有大量点进行动画处理时,文本的不透明度淡入淡出效果会被完全破坏,但仅在使用CSS动画时!

尽管经过良好优化的 JavaScript 在速度上通常与 CSS 动画一样快,甚至可能更快,但 3D 变换在使用 CSS 进行动画时往往会更快,但这与浏览器处理 16 元素矩阵的方式有关(强制将数字转换为连接的字符串,然后再转换回数字)。希望这种情况会有所改变。不过,在大多数实际项目中,您根本不会注意到性能差异。

我建议您进行自己的测试,以查看哪种技术在您的特定项目中提供最流畅的动画。不要相信 CSS 动画总是更快的神话,也不要认为上面的速度测试反映了您在应用程序中看到的实际情况。测试,测试,测试。

运行时控制和事件

某些浏览器允许您暂停/恢复 CSS 关键帧动画,但仅此而已。您无法跳转到动画中的特定位置,也无法在中途平滑反转或更改时间尺度,或者在特定位置添加回调或将其绑定到丰富的回放事件集。JavaScript 提供了强大的控制,如下面的演示所示。

查看 GreenSock 在 CodePen 上创建的 Pen CSS 无法实现:控制 (@GreenSock)

现代动画与交互性密切相关,因此能够从可变的起始值动画到可变的结束值(例如,基于用户点击的位置)或动态更改内容非常有用,但声明性的基于 CSS 的动画无法做到这一点。

工作流程

对于两个状态之间简单的过渡(例如,鼠标悬停或展开菜单等),CSS 过渡非常棒。但是,对于顺序排列内容,您通常需要使用 CSS 关键帧动画,这会迫使您以百分比定义内容,例如

@keyframes myAnimation {
  0% {
    opacity: 0;
    transform: translate(0, 0);
  }
  30% {
    opacity: 1;
    transform: translate(0, 0);
  }
  60% {
    transform: translate(100px, 0);
  }
  100% {
    transform: translate(100px, 100px);
  }
}
#box {
   animation: myAnimation 2.75s;
}

但是,当您进行动画制作时,您是否会以时间而不是百分比来思考?例如,“将不透明度淡入 1 秒,然后向右滑动 0.75 秒,然后弹回并静止 1 秒”。如果您花费数小时以百分比精心制作复杂的序列,然后客户说“将中间部分延长 3 秒”会发生什么?哎呀。您需要重新计算所有百分比!

通常,构建动画涉及大量实验,尤其是时间和缓动效果。这实际上是 seek() 方法非常有用的地方。想象一下,分段构建一个 60 秒的动画,然后微调最后 5 秒;每次想要查看对最后部分的编辑结果时,您都需要观看前 55 秒。太糟糕了。使用 seek() 方法,您可以在制作过程中将其放置到位以跳到您正在处理的部分,然后在完成后将其删除。节省大量时间。

基于画布的对象和其他第三方库对象动画正变得越来越普遍,但不幸的是,CSS 动画只能定位 DOM 元素。这意味着,如果您在 CSS 动画上投入了大量时间和精力,它将无法转换为其他类型的项目。您将不得不切换动画工具集。

CSS 动画中还缺少一些与工作流程相关的便利功能。

  • 相对值。例如,“使旋转再增加 30 度”或“将元素从动画开始时的位置向下移动 100px”。
  • 嵌套。想象一下,能够创建可以嵌套到另一个动画中的动画,而该动画本身可以继续嵌套,等等。想象一下控制该主动画,同时所有内容都保持完美同步。这种结构将促进模块化代码的生成和维护,从而更容易实现。
  • 进度报告。某个动画是否已完成?如果没有,它在进度方面究竟处于什么位置?
  • 目标终止。有时,终止影响元素“缩放”(或您想要的任何属性)的所有动画非常有用,同时允许其余动画继续进行。
  • 简洁的代码。即使您没有考虑所有冗余的供应商前缀版本,CSS 关键帧动画也很冗长。任何尝试构建中等复杂内容的人都会证明,CSS 动画很快就会变得繁琐且难以管理。事实上,完成动画任务所需的 CSS 量可能超过 JavaScript 库的权重(JavaScript 库更容易缓存并在许多动画之间重复使用)。

有限的效果

您无法使用 CSS 动画真正实现以下任何操作

  • 沿着曲线(如贝塞尔曲线)进行动画。
  • 使用有趣的缓动效果,如弹性、弹跳或粗糙缓动。有一个 cubic-bezier() 选项,但它只允许 2 个控制点,因此非常有限。
  • 在 CSS 关键帧动画中为不同的属性使用不同的缓动效果;缓动效果应用于整个关键帧。
  • 基于物理的运动。例如,在此 可拖动演示 中实现的平滑的基于动量的轻弹和回弹。
  • 为滚动位置设置动画。
  • 方向旋转(例如,“以最短的方向,顺时针或逆时针,动画到正好 270 度”)。
  • 为属性设置动画。

兼容性

基于 CSS 的动画在IE9 及更早版本不起作用。我们大多数人都讨厌支持旧版浏览器(尤其是 IE),但现实情况是,我们中的一些人有客户需要这种支持。

许多浏览器都需要浏览器前缀,但您可以利用预处理工具来避免手动编写它们。

结论

CSS 动画“不好”吗?当然不是。事实上,当不需要与旧版浏览器的兼容性时,它们非常适合状态之间的简单过渡(例如鼠标悬停)。3D 变换通常表现出色(iOS7 是一个值得注意的例外),对于希望将所有动画和表示逻辑都放在 CSS 层的开发人员来说,CSS 动画非常有吸引力。但是,基于 JavaScript 的动画提供了更多的灵活性、更适合复杂动画和丰富交互性的工作流程,并且它通常与基于 CSS 的动画一样快(甚至更快),尽管您可能听说过其他说法。

jQuery.animate() 相比,我可以理解为什么 CSS 动画如此吸引人。有什么理由不抓住机会获得 10 倍的性能提升呢?但现在不再是在 jQuery 和 CSS 动画之间进行选择;像 GSAP 这样的基于 JavaScript 的工具开辟了全新的可能性,并消除了性能差距。

本文不是关于 GSAP 或任何特定库;重点是基于 JavaScript 的动画不应有坏名声。事实上,JavaScript 是真正强大、灵活的动画系统的唯一选择。此外,我想阐明 CSS 动画令人沮丧的部分(似乎没有人谈论这些),以便您最终能够对如何在浏览器中进行动画制作做出更明智的决定。

Web 动画规范能否解决问题?

W3C 正在开发一项名为 Web 动画 的新规范,旨在解决 CSS 动画和 CSS 过渡中的许多缺陷,提供更好的运行时控制和额外功能。从许多方面来看,这无疑是一项进步,但它仍然存在不足之处(其中一些可能无法克服,因为需要向后兼容现有的 CSS 规范,例如,独立变换组件控制不太可能实现)。不过,那是另一篇文章的内容。我们将拭目以待事情的发展。确实有一些聪明的家伙在研究该规范。