Chromium 84 中的 Web Animations API 改进

使用 promise 编排动画,通过可替换动画提升性能,使用复合模式让动画更流畅,等等。

Kevin Ellis
Kevin Ellis

如果使用得当,动画可改善用户对品牌的认知和记忆、引导用户操作并帮助用户浏览您的应用,从而在数字空间中提供背景信息。

Web Animations API 是一款工具,可让开发者使用 JavaScript 编写命令式动画。编写此代码是为了同时支持 CSS 动画和过渡的实现,并支持开发未来的效果以及组合和计时的现有效果。

FirefoxSafari 已经实现了所有规范功能,而 Chromium 84 为 Chrome 和 Edge 带来了许多以前不受支持的功能,可实现跨浏览器互操作性。

2014 年 7 月,Web Animations API 在版本 36 中首次登陆 Chromium。现在,该规范即将完成,即 2020 年 7 月发布的版本 84。
Chromium 中 Web Animations API 的悠久历史。

使用入门

如果您使用过 @keyframe 规则,应该非常熟悉通过 Web Animations API 创建动画。首先,您需要创建一个关键帧对象。CSS 中的效果示例:

@keyframes openAnimation {
  0% {
    transform: scale(0);
  }
  100% {
    transform: scale(1);
  }
}

在 JavaScript 中如下所示:

const openAnimation = [
  { transform: 'scale(0)' },
  { transform: 'scale(1)' },
];

在 CSS 中为动画设置参数的位置:

.modal {
  animation: openAnimation 1s 1 ease-in;
}

进行如下设置:

document.querySelector('.modal').animate(
    openAnimation, {
      duration: 1000, // 1s
      iterations: 1, // single iteration
      easing: 'ease-in' // easing function
    }
);

代码量大致是相同的,但使用 JavaScript,您获得了一些单靠 CSS 所不具备的超能力。这包括对效果进行排序的功能,以及更好地控制其播放状态的功能。

element.animate()以外

不过,经过更新后,Web Animations API 不再局限于通过 element.animate() 创建的动画。我们还可以操纵 CSS 动画和过渡。

getAnimations() 方法是返回某个元素的所有动画,无论该元素是通过 element.animate() 创建还是通过 CSS 规则(CSS 动画或过渡)创建。如下所示:

首先,您需要 "get" 转场的关键帧,以确定我们从何处进行转场。然后,您再制作两个新的不透明度动画,分别实现淡入淡出效果。淡入淡出后,删除副本。

使用 promise 编排动画

在 Chromium 84 中,您现在有两个可与 promise 搭配使用的方法:animation.readyanimation.finished

  • animation.ready 可让您等待待更改项生效(即在播放和暂停等播放控制方法之间切换)。
  • animation.finished 提供了一种在动画播放完毕后执行自定义 JavaScript 代码的方法。

我们继续看这个示例,并使用 animation.finished 创建一个编排的动画链。在此示例中,您有一个垂直转换 (scaleY),接着是水平转换 (scaleX),接着是对子元素的不透明度变化:

对开合模态元素应用变形和不透明度。观看 Codepen 演示
const transformAnimation = modal.animate(openModal, openModalSettings);
transformAnimation.finished.then(() => { text.animate(fadeIn, fadeInSettings)});

在执行动画链中的下一个动画集之前,我们已使用 animation.finished.then() 将这些动画链接起来。这样一来,动画就会按顺序显示,您甚至可以将效果应用到设置了不同选项(例如速度和轻松程度)的不同目标元素。

在 CSS 中,重新创建起来很麻烦,尤其是在对多个元素应用唯一但有序的动画时。您必须使用 @keyframe,列出正确的计时百分比来放置动画,并在触发序列中的动画之前使用 animation-delay

示例:播放、暂停和倒放

能打开的,应该关闭的!幸运的是,自 Chromium 39 起,Web Animations API 为我们提供了播放、暂停和倒放动画的功能。

您可以截取上面的动画,并在再次点击该按钮时使用 .reverse() 为其赋予流畅的倒转动画效果。这样,你可以为模态窗口创建更顺畅、更符合情境的互动。

在用户点击按钮时打开和关闭模态窗口的示例。观看 Glitch 演示

您可以创建两个待播放动画(openModal 和内嵌不透明度转换),然后暂停其中一个动画,将其延迟,直到另一个动画完成。然后,您可以使用 promise 来等待每个任务完成后再播放。最后,您可以检查是否设置了标志,然后倒放每个动画。

示例:与部分关键帧进行动态互动

重访定位示例,其中点击鼠标将动画调整到新的位置。观看 Glitch 演示
selector.animate([{transform: `translate(${x}px, ${y}px)`}],
    {duration: 1000, fill: 'forwards'});

在该示例中,只有一个关键帧,且未指定起始位置。这是一个使用部分关键帧的示例。鼠标处理程序会在此处执行一些操作:设置新的结束位置并触发新的动画。系统会根据当前的底层位置推断新的开始位置。

新的转换可以在现有转换仍在运行时触发。这意味着当前转换会中断,并且系统会创建新的转换。

通过可替换的动画提升了性能

基于事件(例如基于 'mousemove')创建动画时,每次都会创建一个新动画,这可能会快速消耗内存并降低性能。为了解决此问题,Chromium 83 中引入了可替换的动画,从而实现了自动清理,其中已完成的动画会被标记为可替换,并且会在被其他已完成的动画替换后自动移除。请参考以下示例:

鼠标移动时,彗星轨迹会以动画形式呈现。观看 Glitch 演示
elem.addEventListener('mousemove', evt => {
  rectangle.animate(
    { transform: translate(${evt.clientX}px, ${evt.clientY}px) },
    { duration: 500, fill: 'forwards' }
  );
});

每次移动鼠标时,浏览器都会重新计算彗星轨迹中每个球的位置,并为这个新点创建动画。现在,浏览器可以在以下情况下移除旧动画(启用替换):

  1. 动画播放完毕。
  2. 复合排序中有一个或多个动画也已完成。
  3. 新动画将为相同的属性添加动画效果。

通过将计数器与每个已移除的动画相关联,您可以使用 anim.onremove 触发计数器,您可以查看替换的动画的确切数量。

您还可以通过其他一些方法来进一步优化动画控件:

  • animation.replaceState() 提供了一种方法来跟踪动画是处于活跃状态、持续播放还是已移除。
  • animation.commitStyles() 会根据底层样式以及元素上的所有动画以复合顺序更新元素的样式。
  • animation.persist() 用于将动画标记为不可替换。

使用复合模式可使动画更流畅

现在,您可以使用 Web Animations API 设置动画的复合模式,这意味着除了默认“替换”模式外,动画还可以叠加或累计。复合模式让开发者可以编写独特的动画,并控制效果的组合方式。现在支持三种复合模式:'replace'(默认模式)、'add''accumulate'

当您合成动画时,开发者可以编写简短、独特的效果,并看到它们组合在一起。在以下示例中,我们要将旋转和缩放关键帧应用于每个框,唯一的调整是合成模式,添加为选项:

此演示显示了“默认”“添加”和“累积”复合模式。观看 Glitch 演示

在默认的 'replace' 复合模式下,最终动画将替换转换属性,并结束于 rotate(360deg) scale(1.4)。对于 'add',合成会添加旋转角度并乘以缩放,从而得到最终状态为 rotate(720deg) scale(1.96)'accumulate' 组合转换,生成 rotate(720deg) scale(1.8)。如需详细了解这些复合模式的复杂性,请参阅网页动画规范中的 CompositeOperation 和 CompositeOperationOrAuto 枚举

我们来看一个界面元素示例:

应用了两个合成动画的弹跳下拉菜单。观看 Glitch 演示

在此示例中,我们合成了两个 top 动画。第一个是宏动画,会将下拉菜单按菜单本身的整个高度移动,呈现为从页面顶部滑入的效果;第二个则是微动画,在贴近底部时应用略微弹跳。使用 'add' 复合模式可以实现更顺畅的过渡。

const dropDown = menu.animate(
    [
      { top: `${-menuHeight}px`, easing: 'ease-in' },
      { top: 0 }
    ], { duration: 300, fill: 'forwards' });

  dropDown.finished.then(() => {
    const bounce = menu.animate(
      [
        { top: '0px', easing: 'ease-in' },
        { top: '10px', easing: 'ease-out' },
        { ... }
      ], { duration: 300, composite: 'add' });
  });

Web Animations API 的未来发展趋势

这些都是对当今浏览器中动画功能的激动人心的新增功能,并且正在陆续推出更多功能。请查看这些未来的规范,进一步了解即将推出的功能: