Safari 13.1 中的 Web 动画
随着 iOS 13.4、iPadOS 13.4 和 macOS Catalina 10.15.4 中 Safari 13.1 的发布,Web 开发者拥有了一个新的 API:Web Animations(Web 动画)。我们已经为此功能工作了两年多,它现在已向所有 Safari 用户开放,提供了一个出色的编程 API,用于使用 JavaScript 创建和控制动画。
在本文中,我们将讨论这个新 API 的优点、如何最好地检测其可用性、它如何与现有功能(例如 CSS 动画和 CSS 过渡)集成,以及 WebKit 中动画技术未来的发展方向。
一点历史
WebKit 团队早在 2007 年就提出了 CSS 动画和 CSS 过渡的原始提案,并在本博客上公布。多年来,这些规范已经成熟,成为 W3C 标准,并成为 Web 平台不可或缺的一部分。
借助这些技术,在 Web 内容中集成动画变得简单,消除了开发人员编写 JavaScript 的要求,同时通过允许浏览器在可用时使用硬件加速并将动画集成到布局和渲染管道中,提供了更好的性能和功耗。
作为一名 Web 开发者,我一直很喜欢 CSS 动画和 CSS 过渡的简单性和出色性能。我相信正是这些优点使动画成为 Web 开发者强大的工具。然而,在我的日常工作中,我也发现这些技术在某些方面令人沮丧:动态创建、播放控制以及监视动画的生命周期。
好消息是,这些问题都可以通过新的 Web Animations API 解决。让我们看看如何利用这个新 API 来改进这些领域的日常代码。
第一部分 – 动画创建
虽然 CSS 可以让你非常轻松地为状态变化(例如按钮的出现)添加动画,但如果给定动画的开始和结束值事先未知,则会棘手得多。通常,Web 开发者会使用 CSS 过渡来处理这些情况。
// Set the transition properties and start value.
element.style.transitionProperty = "transform";
element.style.transitionDuration = "1s";
element.style.transform = "translateX(0)";
// Force a style invalidation such that the start value is recorded.
window.getComputedStyle(element);
// Now, set the end value.
element.style.transform = "translateX(100px)";
尽管这看起来是合理的代码量,但还有其他因素需要考虑。强制样式失效将不会让浏览器在它认为最合适的时间执行该任务。这还只是一个动画;如果页面的另一部分,甚至是一个完全不同的 JavaScript 库,也需要创建动画怎么办?这将导致强制样式失效成倍增加,并降低性能。
如果你考虑改用 CSS 动画,你将不得不首先生成一个专用的 @keyframes
规则并将其插入到 <style>
元素中,这样就无法封装真正针对单个元素的样式更改,并导致昂贵的样式失效。
Web Animations API 的价值在于它提供了一个 JavaScript API,该 API 保留了让浏览器引擎高效运行动画的繁重工作能力,同时实现了对动画更高级的控制。使用 Web Animations API,我们可以使用 Element.animate()
通过单个方法调用重写上述代码。
element.animate({ transform: ["translateX(0)", "translateX(100px)"] }, 1000);
尽管这个例子非常简单,但单个的 Element.animate()
方法简直是万能的瑞士军刀,可以表达更高级的功能。第一个参数指定 CSS 值,而第二个参数指定动画的时间。我们不会深入探讨所有可能的时间属性,但 CSS 动画的所有功能都可以使用 Web Animations API 来表达。例如:
element.animate({
transform: ["translateX(500px)"], // move by 500px
color: ["red", "blue", "green"] // go through three colors
}, {
delay: 500, // start with a 500ms delay
easing: "ease-in-out", // use a fancy timing function
duration: 1000, // run for 1000ms
iterationCount: 2, // repeat once
direction: "alternate" // run the animation forwards and then backwards
});
现在我们知道如何使用 Web Animations API 创建动画了,但它比使用 CSS 过渡的代码片段好在哪里呢?嗯,那段代码告诉浏览器要动画化什么以及如何动画化,但没有指定何时。现在,浏览器将能够在下一个最合适的时机处理所有新动画,而无需强制样式失效。这意味着你自己编写的动画以及可能来自第三方 JavaScript 库——甚至来自不同文档(例如,通过 <iframe>
)——的所有动画都将同步开始并同步进行。
第二部分 – 播放控制
现有技术的另一个缺点是缺乏播放控制:即暂停、恢复和定位动画以及控制其速度的能力。虽然 animation-play-state
属性允许控制 CSS 动画是暂停还是播放,但对于 CSS 过渡却没有等效功能,而且它只控制播放控制的一个方面。如果你想设置动画的当前时间,你只能求助于迂回的技术,例如巧妙地操作负的 animation-delay
值,如果你想改变动画播放的速度,唯一的选择就是操作时间值。
借助 Web Animations API,所有这些问题都由专用 API 处理。例如,我们可以使用 play()
和 pause()
方法来操作播放状态,使用读写 currentTime
属性来查询和设置时间,以及使用 playbackRate
在不修改持续时间的情况下控制速度。
// Create an animation and keep a reference to it.
const animation = element.animate(…);
// Pause the animation.
animation.pause();
// Change its current time to move forward by 500ms.
animation.currentTime += 500;
// Slow the animation down to play at half-speed.
animation.playbackRate = 0.5;
这使得开发人员可以在动画创建后控制其行为。现在,执行以前令人生畏的任务变得轻而易举。例如,通过按下按钮来切换动画的播放状态:
button.addEventListener("click", event => {
if (animation.playState === "paused")
animation.play();
else
animation.pause();
});
将动画的进度连接到 <input type="range">
元素:
input.addEventListener("input", event => {
animation.currentTime = event.target.value * animation.effect.getTiming().duration;
});
感谢 Web Animations API 将播放控制作为动画的核心概念,这些简单的任务变得轻而易举,并且可以实现对动画状态更复杂的控制。
第三部分 – 动画生命周期
尽管 transition*
和 animation*
系列 DOM 事件提供了关于 CSS 源动画何时开始和结束的信息,但很难正确使用它们。考虑在从 DOM 中移除元素之前将其淡出。通常,使用 CSS 动画会这样编写:
@keyframes fade-out {
to { opacity: 0 }
}
element.style.animationName = "fade-out";
element.addEventListener("animationend", event => {
element.remove();
});
看起来是正确的,但进一步检查会发现问题。这段代码会在元素上触发 animationend
事件后立即移除该元素,但由于动画事件会冒泡,该事件可能来自 DOM 层次结构中子元素的动画完成,而且动画甚至可能同名。你可以采取措施使这类代码更安全,但使用 Web Animations API,编写这类代码不仅更容易,而且更安全,因为你直接引用了一个 Animation
对象,而不是通过作用于元素层次结构的动画事件来操作。最重要的是,Web Animations API 使用 Promise 来监视动画的 ready
和 finished
状态。
let animation = element.animate({ opacity: 0 }, 1000);
animation.finished.then(() => {
element.remove();
});
考虑一下,如果你想在移除一个共享容器之前监视针对多个元素的多个 CSS 动画的完成情况,同样的任务会变得多么复杂。有了 Web Animations API 及其对 Promise 的支持,现在可以简洁地表达这一点:
// Wait until all animations have finished before removing the container.
let animations = container.getAnimations();
Promise.all(animations.map(animation => animation.finished).then(() => {
container.remove();
});
与 CSS 集成
Web Animations 的设计目的不是取代现有技术,而是与它们紧密集成。你可以自由选择最适合你的用例和偏好的技术。
Web Animations 规范不仅定义了一个 API,还旨在为 Web 上的动画提供一个共享模型;处理动画的其他规范也使用相同的模型和术语定义。因此,最好将 Web Animations 理解为 Web 上动画的基础,并将其 API 以及 CSS Transitions 和 CSS Animations 视为该共享基础之上的层。
这在实践中意味着什么?
为了实现 Web Animations API 的出色实现,我们不得不从头开始,为 CSS 动画、CSS 过渡和新的 Web Animations API 创建一个全新且共享的动画引擎。即使您不使用 Web Animations API,您编写的 CSS 动画和 CSS 过渡现在也都在新的 Web Animations 引擎中运行。无论您选择哪种技术,所有动画都将同步运行和更新,由 CSS 源动画和 Web Animations API 触发的事件将同时传递,等等。
但对作者来说更重要的是,整个 Web Animations API 可用于查询和控制 CSS 源动画!您可以在纯 CSS 中指定动画,但也可以使用 Web Animations API 通过 Document.getAnimations()
和 Element.getAnimations()
来控制它们。您可以以这种方式暂停给定文档中运行的所有动画:
document.getAnimations().forEach(animation => animation.pause());
SVG 呢?现阶段,SVG 动画仍然与 Web Animations 模型不同,Web Animations API 和 SVG 之间没有集成。这仍然是 Web 平台需要改进的领域。
特性检测
但在您开始在项目中采用这项新技术之前,还有一些进一步的实际考虑因素需要注意。
由于这是新技术,随着用户逐步将浏览器更新到支持 Web Animations 的新版本,使用特性检测非常重要。检测各种 Web Animations API 的可用性很简单。以下是检测 Element.animate()
可用性的一种正确方法:
if (element.animate)
element.animate(…); // Use the Web Animations API.
else
… // Fall back to other technologies.
尽管 Safari 正在整体推出 Web Animations API,但其他浏览器(例如 Firefox 和 Chrome)早已推出 Element.animate()
,因此单独测试各个特性至关重要。所以,如果您想使用 Document.getAnimations()
查询给定 document
的所有正在运行的动画,请务必进行该特性的可用性检测。因此,上面的代码片段最好这样编写:
if (document.getAnimations)
document.getAnimations().forEach(animation => animation.pause());
else
… // Fall back to another approach.
该 API 的某些部分尚未在 Safari 中实现。值得注意的是,效果合成(effect composition) 尚未支持。在定义动画时间时尝试设置 composite
属性之前,您可以这样检查它是否受支持:
const isEffectCompositionSupported = !!(new KeyframeEffect(null, {})).composite;
Web 检查器中的动画
最新 Safari 版本中的新功能:CSS 动画和 CSS 过渡可以在 Web 检查器中“时间轴”选项卡的新“媒体与动画”时间轴中查看,按每个元素目标的 animation-name
或 transition-property
属性进行组织。与其他时间轴一起使用时,通过查看 JavaScript 和事件时间轴中的脚本条目,可以帮助关联特定 CSS 动画或 CSS 过渡是如何创建的。

从 Safari 技术预览版 100 开始,Web 检查器在“图形”选项卡中显示所有动画,无论它们是由 CSS 创建的还是使用 JavaScript API 创建的。它以延迟的线条和关键帧的曲线可视化每个单独的动画对象,并提供了动画将做什么的深入视图,以及有关其创建方式的信息和一些有用的操作,例如在控制台中记录动画对象。这些是 Web Animations 允许改进 Web 检查器用于动画工作的首批示例,我们期待进一步改进我们的工具。

前进之路
发布对 Web Animations 的支持是 WebKit 中动画领域的一个重要里程碑。我们新的动画引擎提供了一个更符合规范且更具前瞻性的代码库以供改进。这正是像您这样的开发者发挥作用的地方:我们很希望能听到您在使用现有内容中的 CSS 动画和 CSS 过渡时遇到的任何兼容性问题,以及在您的内容中采用新的 Web Animations API 时遇到的问题。
过渡到新的 Web Animations 引擎使我们能够解决已知的回归问题,并在 Web 平台测试方面取得了许多进展,从而改善了跨浏览器兼容性。如果您发现您的动画在 Safari 中的运行方式不同,请在 bugs.webkit.org 上提交错误报告,以便我们诊断问题并确定这是行为的有意更改还是我们需要解决的回归问题。
我们已经在努力改进这个初始版本,您可以通过关注本博客和每个新的 Safari 技术预览版发布说明来关注未来的改进。
您还可以向 @webkit 或 @jonathandavis 发送推文,分享您对我们新支持 Web Animations 的看法。