增强您的 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 the
durationoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline's
currentTimeis
startTime + 3000 + 1000and the last keyframe at
startTime + 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)
);
}
}
);
我们获取 currentTime
的 Math.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 就是您的后盾!