Houdini 的动画 Worklet

增强您的 Web 应用动画

要点:通过动画 Worklet,您可以编写运行命令式动画 以设备的原生帧速率实现额外的流畅性 TM, 使动画在遇到主线程卡顿问题时更有弹性,并且可链接 而不是时间动画 Worklet 在 Chrome Canary 版中( "实验性网络平台功能"标志),并计划针对 Chrome 71 进行源试用。您可以开始以如下身份使用它: 立即推出一项渐进式增强功能。

其他动画 API?

实际上不是,这是对我们现有功能的扩展,有充分的理由! 我们从头开始吧。如果您想为网络上的任何 DOM 元素添加动画效果 目前,您有两种选择:CSS 过渡 简单的 A 到 B 转场,CSS 动画 可能循环的、更复杂的基于时间的动画和 Web Animations API (WAAPI) 适用于几乎任意复杂的动画。WAAPI 的支持矩阵看起来相当可怕,但 它在路上了在此之前, polyfill

所有这些方法的共同点是它们都是无状态方法, 。但开发者尝试的一些效果 由时间驱动的还是无状态的例如,著名的视差滚动条是 即由滚动驱动目前,在网络上实现高效的视差滚动条并非易事。

无状态呢?以 Chrome 的地址栏为例, 示例。如果您向下滚动,屏幕会滚动出视图。但是 当您向上滚动页面时 即使您已浏览了一半 再往下走动画不仅取决于滚动位置,还取决于 之前的滚动方向它是有状态的。

另一个问题是滚动条的样式设置。众所周知,它们设计不拘一格,甚至 至少样式不够。如果我想一只猫咪作为我的滚动条,该怎么做? 无论您选择哪种方法,构建自定义滚动条都不是 也不简单

关键在于,所有这些事情都尴尬且难以实现 高效实施。其中大多数依赖于事件和/或 requestAnimationFrame,即使屏幕为 60fps, 能够以 90fps、120fps 或更高的速率运行,并且 宝贵的主线程帧预算

Animation Worklet 扩展了 Web 动画堆栈的功能, 从而更轻松地解决此类影响在深入了解相关内容之前 了解动画基础知识。

动画和时间轴入门指南

WAAPI 和动画 Worklet 广泛使用时间轴,让您可以 根据需要编排动画和效果。此部分为 快速回顾或介绍时间轴及其与动画的处理方式。

每个文档都有 document.timeline。当文档 ,并计算自文档开始现有文档之后的毫秒数。以下所有权限: 文档的动画相对于此时间轴发挥作用。

为了更具体地说明,我们来看看此 WAAPI 代码段

const animation = new Animation(
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
      {
        transform: 'translateY(500px)',
      },
    ],
    {
      delay: 3000,
      duration: 2000,
      iterations: 3,
    }
  ),
  document.timeline
);

animation.play();

当我们调用 animation.play() 时,动画会使用时间轴的 currentTime 作为开始时间动画有 3000 毫秒的延迟 当时间轴达到“startTime”时,动画将开始(或变为“活动状态”)

  • 3000. After that time, the animation engine will animate the given element from the first keyframe (translateX(0)), through all intermediate keyframes (translateX(500px)) all the way to the last keyframe (translateY(500px)) in exactly 2000ms, as prescribed by thedurationoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline'scurrentTimeisstartTime + 3000 + 1000and the last keyframe atstartTime + 3000 + 2000`。重点是, 时间轴可以控制动画所处的位置!

动画到达最后一个关键帧后,就会跳回到第一个关键帧 然后开始动画的下一次迭代。此过程会重复 自设置 iterations: 3 以来总共重复了 3 次。如果我们希望动画效果 永不停止,我们将写入 iterations: Number.POSITIVE_INFINITY。以下是 结果

WAAPI 功能极其强大,此 API 中还有其他很多功能,比如 动画、起始偏移量、关键帧权重和填充行为 本文的范围。如果您想了解更多信息,建议您阅读这篇有关 CSS 动画的 CSS 技巧文章

编写动画 Worklet

现在,我们已经了解了时间轴的概念,可以开始关注 动画 Worklet 以及它如何让您摆弄时间轴!动画 Worklet API 不仅基于 WAAPI,而且从可扩展网页的意义上说,是一种较低级别的基元, 介绍了 WAAPI 的运作方式。它们在语法方面非常相似:

动画 Worklet WAAPI
new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)'
      },
      {
        transform: 'translateX(500px)'
      }
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY
    }
  ),
  document.timeline
).play();
      
        new Animation(

        new KeyframeEffect(
        document.querySelector('#a'),
        [
        {
        transform: 'translateX(0)'
        },
        {
        transform: 'translateX(500px)'
        }
        ],
        {
        duration: 2000,
        iterations: Number.POSITIVE_INFINITY
        }
        ),
        document.timeline
        ).play();
        

区别在于第一个参数,即 worklet 的名称 来生成动画

功能检测

Chrome 是首款提供此功能的浏览器,因此请确保您的浏览器 代码并不只是预期会有 AnimationWorklet。因此,在加载 Worklet,我们应检测用户的浏览器是否支持 AnimationWorklet

if ('animationWorklet' in CSS) {
  // AnimationWorklet is supported!
}

加载 Worklet

Worklet 是 Houdini 任务组推出的一个新概念, 使新 API 更易于构建和扩展。我们将详细介绍 但为简单起见,您可以将其想象成便宜, 轻量级线程(如 worker)

我们需要确保已加载名为“passthrough”的 worklet, 在声明动画之前:

// index.html
await CSS.animationWorklet.addModule('passthrough-aw.js');
// ... WorkletAnimation initialization from above ...

// passthrough-aw.js
registerAnimator(
  'passthrough',
  class {
    animate(currentTime, effect) {
      effect.localTime = currentTime;
    }
  }
);

这里发生了什么?我们使用 AnimationWorklet 的 registerAnimator() 调用,并将其命名为“passthrough”。 该名称与我们在上面的 WorkletAnimation() 构造函数中使用的名称相同。部署 注册完成后,addModule() 返回的 promise 将解析并 我们可以开始使用该 Worklet 创建动画。

系统会针对每一帧调用实例中的 animate() 方法 浏览器要渲染,并传递动画时间轴的 currentTime 以及当前正在处理的效果我们只有一个 KeyframeEffect,我们使用 currentTime 来设置效果的 localTime,因此该 Animator 称为“直通式”。使用此代码 使用 Worklet,WAAPI 和 AnimationWorklet 的行为正是 如您所见, 演示

时间

animate() 方法的 currentTime 参数是currentTime 传递给 WorkletAnimation() 构造函数的时间轴。在上一个 我们只是让这个时间生效但由于 JavaScript 代码,而且我们可以扭曲时间 💫?

function remap(minIn, maxIn, minOut, maxOut, v) {
  return ((v - minIn) / (maxIn - minIn)) * (maxOut - minOut) + minOut;
}
registerAnimator(
  'sin',
  class {
    animate(currentTime, effect) {
      effect.localTime = remap(
        -1,
        1,
        0,
        2000,
        Math.sin((currentTime * 2 * Math.PI) / 2000)
      );
    }
  }
);

我们获取 currentTimeMath.sin(),并将该值重新映射到 范围为 [0;2000],这是定义影响的时间范围。现在 动画看起来会大不一样 更改了关键帧或动画选项Worklet 代码可以是 任意复杂度,并且可让您以编程方式定义 顺序和程度

选项而非选项

您可能需要重复使用一个 Worklet 并更改其编号。因此, WorkletAnimation 构造函数允许您将选项对象传递给 Worklet:

registerAnimator(
  'factor',
  class {
    constructor(options = {}) {
      this.factor = options.factor || 1;
    }
    animate(currentTime, effect) {
      effect.localTime = currentTime * this.factor;
    }
  }
);

new WorkletAnimation(
  'factor',
  new KeyframeEffect(
    document.querySelector('#b'),
    [
      /* ... same keyframes as before ... */
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY,
    }
  ),
  document.timeline,
  {factor: 0.5}
).play();

在此示例中, 两个动画都由相同的代码驱动,但选项不同。

向您所在的省级行政区提供这项信息!

正如我之前提到的,动画 Worklet 旨在解决的一个关键问题是 有状态动画动画 Worklet 可以保留状态。不过, 使用 Worklet 的核心功能就是 甚至被销毁以节省资源, 状态。为了防止状态丢失,动画 Worklet 提供了一个钩子, 在 Worklet 被销毁之前调用,您可以使用该 Worklet 返回状态 对象。当 Worklet 被激活时,该对象将传递给构造函数 已重新创建。初次创建时,该参数将为 undefined

registerAnimator(
  'randomspin',
  class {
    constructor(options = {}, state = {}) {
      this.direction = state.direction || (Math.random() > 0.5 ? 1 : -1);
    }
    animate(currentTime, effect) {
      // Some math to make sure that `localTime` is always > 0.
      effect.localTime = 2000 + this.direction * (currentTime % 2000);
    }
    destroy() {
      return {
        direction: this.direction,
      };
    }
  }
);

每次刷新此演示版时,您得到的比例为 50/50 正方形旋转方向的可能性。如果浏览器崩溃了 Worklet 并迁移到其他线程,还会再创建另一个 Math.random() 调用,这可能会导致 。为了确保不会发生这种情况,我们会返回动画 随机选择的方向作为 state,并在构造函数中使用该方向(如果提供)。

融入时空连续性:滚动时间轴

如上一部分所示,通过 AnimationWorklet,我们可以 程序化地定义推进时间轴如何影响 动画。但到目前为止,我们的时间轴一直是 document.timeline, 并跟踪时间

ScrollTimeline 带来了新的可能性,并支持您驱动动画 浏览网页,而非时间我们将重复使用第一个 “passthrough”此 Worklet demo

new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
    ],
    {
      duration: 2000,
      fill: 'both',
    }
  ),
  new ScrollTimeline({
    scrollSource: document.querySelector('main'),
    orientation: 'vertical', // "horizontal" or "vertical".
    timeRange: 2000,
  })
).play();

我们将创建新的 ScrollTimeline,而不是传递 document.timeline。 您可能已经猜到了,ScrollTimeline 不会耗费时间, scrollSource 的滚动位置,以设置 Worklet 中的 currentTime。正在 一直滚动到顶部(或左侧)表示 currentTime = 0,而 一直滚动到底部(或右侧)时,系统会将 currentTime 设为 timeRange。如果您滚动此 演示,您可以 控制红色框的位置。

如果您使用不滚动的元素创建 ScrollTimeline, 时间轴的currentTime将为NaN。尤其是 请注意,您应始终准备好将 NaN 作为您的 currentTime。通常, 默认为 0。

长期以来,开发者一直在寻求通过滚动位置来关联动画, 但实际上从来没有达到这种保真度级别(除了 使用 CSS3D 解决方法)。动画 Worklet 允许 轻松简单实现,并保持出色的性能。例如: 像这样视差滚动效果 demo 可以 现在只需几行代码即可定义滚动驱动的动画。

深入了解

Worklet

Worklet 是一种 JavaScript 上下文,具有隔离的范围和一个非常小的 API 。较小的 API Surface 可从 尤其是在低端设备上此外,worklet 也不会 特定事件循环,但可以根据需要在线程之间移动。这是 对 AnimationWorklet 尤其重要。

合成器 NSync

您可能知道,某些 CSS 属性可快速添加动画效果,而其他属性则 错误。有些属性只需要在 GPU 上做一些处理才能设置动画效果,而其他属性则 强制浏览器重新布局整个文档

Chrome(和其他许多浏览器一样)有一个名为合成器的进程, 由谁负责呢?在这里,我要简化一下: 然后利用 GPU 尽可能定期更新屏幕 理想情况下与屏幕的更新速度一样快(通常为 60Hz)。这取决于 对 CSS 属性进行动画处理,浏览器可能只需要 而其他属性则需运行布局 只有主线程可以执行的操作具体取决于您要为 您的动画 Worklet 要么绑定到主 线程或在单独的线程中运行(与合成器同步)。

拍在手腕

通常只有一个合成器进程 因为 GPU 是一种竞争激烈的资源。如果合成器 由于某种原因,整个浏览器都停止运行, 用户输入。必须竭尽全力避免这种情况。那么,如果你的 Worklet 无法及时传送合成器在渲染帧时所需的数据 ?

如果发生这种情况,则根据规范,允许“slip”使用 Worklet。落后于 并且允许合成器重复使用最后一帧的数据 保持较高的帧速率从直观上看,这看起来像是卡顿,但 不同之处在于浏览器仍然会对用户输入做出响应。

总结

AnimationWorklet 有许多方面及其为网络带来的好处。 显而易见的优势在于,可以更好地控制动画和采用新的 动画,让网页的视觉保真度更上一层楼。但 API 让应用在降低卡顿的同时 同时获得所有新的善良。

动画 Worklet 目前处于 Canary 阶段,我们的目标是进行源试用 Chrome 71。我们热切期盼您能提供出色的全新网络体验, 我们有待改进的方面。还有一个 polyfill 采用相同的 API,但不提供性能隔离。

请注意,CSS Transitions 和 CSS Animations 仍然有效 而且对于基本动画而言可能要简单得多。但如果您需要 太棒了,AnimationWorklet 就是您的后盾!