Web 动画

W3C 工作草案,

有关本文档的更多详细信息
此版本:
https://www.w3.org/TR/2023/WD-web-animations-1-20230605/
最新发布版本:
https://www.w3.org/TR/web-animations-1/
编辑草案:
https://drafts.csswg.org/web-animations-1/
先前版本:
历史:
https://www.w3.org/standards/history/web-animations-1
测试套件:
https://github.com/web-platform-tests/wpt/tree/master/web-animations
反馈:
CSSWG 问题 仓库
规范内嵌问题
编辑:
(受邀 专家)
(Google)
(Google)
(Apple Inc)
前任编辑:
(Google)
(Google)
(Google)
(Google)
为此规范建议编辑:
GitHub 编辑器
参与:
通过 GitHub 修正 文本
加入 Animation at Work Slack 上的“waapi”频道
IRC:W3C IRC 上的 #webanimations

摘要

本规范定义了一种用于同步 Web 页面呈现变化和 对这些变化进行计时的模型。 本规范还定义了一个用于与此模型交互的应用程序编程接口, 并且预计后续规范将定义用于公开这些 功能的声明式方式。

CSS 是一种用于描述结构化文档 (例如 HTML 和 XML) 在屏幕、纸张等介质上呈现方式的语言。

本文档状态

本节描述本文档发布时的状态。 当前 W3C 出版物列表 以及本技术报告的最新修订版本 可在 W3C 技术报告索引 https://www.w3.org/TR/ 中找到。

本文档由 CSS 工作组使用推荐标准 轨道发布为工作草案。 发布为工作草案 并不意味着获得 W3C 及其成员的认可。

本文档为草案文档, 随时可能被其他文档更新、取代 或废弃。 除作为尚在进行中的工作外,不应以其他方式引用本文档。

请通过在 GitHub 中提交问题(首选)来发送反馈, 并在标题中包含规范代码“web-animations”,如下所示: “[web-animations] ……评论摘要……”。 所有问题和评论都会被存档。 或者,也可以将反馈发送到(已存档的)公共邮件列表 www-style@w3.org。

本文档受 2021 年 11 月 2 日 W3C 流程文档管辖。

本文档由一个按照 W3C 专利政策运作的小组制定。 W3C 维护一份与该小组交付成果相关的任何专利 披露的公开列表; 该页面还包含有关如何披露专利的说明。 任何实际知晓某项专利,并认为该专利 包含必要 权利要求的个人,都必须按照 W3C 专利 政策第 6 节披露相关信息。

1. 引言

本节为非规范性内容

Web 动画定义了一个用于支持 Web 平台上的动画和 同步的模型。 其他规范旨在以此模型为基础,并 通过声明式方式公开其功能。 此外,本规范还定义了一个用于 此模型的编程接口,该接口可由支持 脚本的用户代理实现。

1.1. 用例

Web 动画模型旨在提供表达 CSS 过渡 [CSS-TRANSITIONS-1]、 CSS 动画 [CSS-ANIMATIONS-1] 和 SVG [SVG11] 所必需的功能。 因此,Web 动画模型的用例是 这三个规范用例的并集。

编程接口的用例包括以下内容:

检查正在运行的动画

Web 应用通常必须等待某些动画效果完成 后才能更新某些状态。 本规范中的编程接口允许此类应用 等待所有当前正在运行的动画完成, 无论这些动画是由 CSS 过渡、CSS 动画、 SVG 动画定义,还是直接使用编程接口创建的。

// 等待所有动画完成后再移除元素
Promise.all(
  elem.getAnimations().map(animation => animation.finished)
).then(() => elem.remove());

或者,应用可能希望查询动画的播放状态, 而无需等待。

const isAnimating = elem.getAnimations().some(
  animation => animation.playState === 'running'
);
控制正在运行的动画

有时对动画执行播放控制非常有用, 以便它们可以响应外部输入。 例如,在显示模态对话框之前,可能有必要暂停所有现有动画, 以免它们分散用户的 注意力。

// 暂停文档中所有现有动画
for (const animation of document.getAnimations()) {
  animation.pause()
}
从脚本创建动画

虽然可以使用 ECMAScript 通过 requestAnimationFrame [HTML] 执行动画, 但此类动画在 CSS 层叠中的表示方式以及 可执行的性能优化方面,与声明式动画有所不同, 例如在单独的线程上执行动画。 使用 Web 动画编程接口,可以 从脚本创建与声明式动画具有相同行为和性能 特征的动画。

// 快速淡出
elem.animate({ transform: 'scale(0)', opacity: 0 }, 300);
动画调试

在复杂的应用中,可能很难确定一个 元素是如何达到其当前状态的。 Web 动画编程接口可用于检查 正在运行的动画,以回答诸如 “为什么这个元素的 opacity 正在变化?”之类的问题。

// 打印 elem 上所有 opacity 动画的 id
for (const animation of elem.getAnimations()) {
  if (
    animation.effect instanceof KeyframeEffect &&
    animation.effect
      .getKeyframes()
      .some(frame => frame.hasOwnProperty('opacity'))
  ) {
    console.log(animation.id);
  }
}

同样,为了微调动画,通常需要 降低其播放速率并重新播放。

// 减慢并重新播放所有 transform 动画
const transformAnimations = elem.getAnimations().filter(
  animation =>
    animation.effect instanceof KeyframeEffect &&
    animation.effect.getKeyframes().some(
      frame => frame.hasOwnProperty('transform')
    )
);

for (const animation of transformAnimations) {
  animation.currentTime = 0;
  animation.updatePlaybackRate(0.5);
}
测试动画

为了测试使用动画的应用,通常 不适合等待此类动画运行至完成。 相反,更适合将动画定位到特定时间。

// 定位到动画的中点,并检查 opacity 是否为 50%
for (const animation of elem.getAnimations()) {
  const { delay, activeDuration } = animation.effect.getComputedTiming();
  animation.currentTime = delay + activeDuration / 2;
}
assert.strictEqual(getComputedStyle(elem).opacity, '0.5');

// 检查动画完成后加载屏幕是否已隐藏
for (const animation of elem.getAnimations()) {
  animation.finish();
}
// 等待一帧,以便事件处理程序有机会运行
requestAnimationFrame(() => {
  assert.strictEqual(
    getComputedStyle(document.querySelector('#loading')).display, 'none');
});

1.2. 与其他规范的关系

CSS 过渡 [CSS-TRANSITIONS-1]、CSS 动画 [CSS-ANIMATIONS-1] 和 SVG [SVG11] 都提供了 在 Web 页面上生成动画内容的机制。 尽管这三个规范提供许多相似的功能, 但它们使用不同的术语进行描述。 本规范提出了一种抽象动画模型, 涵盖这三个规范的共同功能。 此模型与这些规范当前的行为向后兼容, 因而可以使用此模型来定义这些规范, 而不会产生任何可观察到的变化。

SVG 1.1 中的动画功能是根据 SMIL Animation [SMIL-ANIMATION] 定义的。 通过依据 Web 动画模型定义 SVG 的动画功能, 旨在移除 SVG 与 SMIL Animation 之间的依赖关系。

与动画帧回调(通常称为 “requestAnimationFrame”)[HTML] 一样, 本规范的编程接口部分允许 从脚本创建动画。 然而,使用本规范所定义接口创建的动画, 一旦创建,就完全由用户代理执行, 这意味着它们与由标记定义的动画具有相同的性能特征。 使用此接口,可以以更简单且性能更高的方式 从脚本创建动画。

编程接口中使用的时间值 与动画帧回调 [HTML] 中使用的时间值相对应,并且它们的执行顺序被定义为使两个接口可以 同时使用而不会发生冲突。

本规范的编程接口部分对 HTML [HTML] 中定义的接口做了一些扩展。

1.3. 本规范概述

本规范首先定义一个抽象动画模型。 随后定义一个基于该抽象模型的编程接口。 编程接口根据该抽象模型定义, 并且仅与提供脚本支持的用户代理相关。

2. 规范约定

本规范首先描述抽象概念,例如动画和动画效果,以及属于 它们的属性,例如其播放 速率或迭代 持续时间。 除这些属性外,通常还有用于 更新这些属性的特定过程,例如设置播放速率的过程,或 设置动画 开始时间的过程。

当本规范未明确链接到某个过程时,要求 用户代理更新某个属性的文本,例如“使 animation 的开始时间变为未解析”,应理解为 不调用任何相关过程而直接更新该属性。

并非本规范特有的其他文档约定 在文档约定中描述。

3. Web 动画模型概述

本节为非规范性内容

概括而言,Web 动画模型由两个大体上 相互独立的部分组成,即计时模型和动画 模型。这些部分的作用如下:

计时模型

获取某个时间点,并将其转换为动画单次迭代中 的比例距离,称为迭代 进度。 还会记录迭代索引,因为某些动画在 每次重复时会有所不同。

动画模型

获取计时模型产生的迭代进度值和迭代索引, 并将它们转换为一系列 应用于目标属性的值。

从图形上看,此流程可以表示如下:

Web 动画模型运行方式概览。
Web 动画模型运行方式概览。
当前时间作为输入传递给 计时模型,计时模型会生成迭代 进度值和迭代索引。
这些参数作为输入传递给动画模型, 动画模型会生成 要应用的值。

例如,考虑一个具有以下特征的动画:

前三点应用于计时模型。 在 6 秒这一时刻,它将计算出动画应位于 第二次迭代的中点,并生成结果 0.5。 然后动画模型使用该信息计算宽度。

本规范首先介绍计时模型,然后介绍 动画模型。

4. 计时模型

本节描述并定义 Web 动画 计时模型的行为。

4.1. 计时模型概述

本节为非规范性内容

Web 动画计时模型具有两个特征:它是无状态的,并且是 分层的。

4.1.1. 无状态

Web 动画计时模型通过获取输入时间并 生成输出迭代进度来运行。 由于输出仅基于输入时间,并且独立于 之前的输入,因此该模型可以描述为无状态。 这使该模型具有以下属性:

与帧率无关

由于输出独立于之前的输入,模型 更新的速率不会影响其进度。 只要输入时间与 现实世界时间的进度成比例,动画就会以相同的速率推进, 而不受运行它们的设备性能影响。

与方向无关

由于输入序列并不重要,因此该模型 没有方向性。 这意味着模型可以更新到任意时刻, 而不需要任何特殊处理。

常数时间定位

由于每个输入均独立于之前的输入, 因此即使定位到很远的未来, 执行定位操作所需的处理至少有可能是常数时间的。

计时模型的无状态行为存在少数例外。

首先,在模型的编程接口中定义的许多方法 提供了播放控制,例如暂停动画。 这些方法是根据调用它们的时间 定义的,因此是有状态的。 这些方法主要是为了方便而提供,并不属于 核心计时模型,而是构建在其之上。

类似地,动画的结束行为 意味着对动画媒体(关联效果)结束时间的动态更改,可能会根据更改发生的时间而产生 不同的结果。 这种行为并不十分理想,但被认为符合直觉 且与 HTML 保持一致。 因此,只有在其计时属性没有动态变化的情况下, 才能真正将该模型描述为无状态。

最后,每次更新模型时,都可以认为它 建立了一个临时状态。 虽然此临时状态会影响编程接口返回的值,但它不会 影响后续更新,因此不会与 上述无状态特性相冲突。

4.1.2. 分层

计时模型的另一个特征是时间可以继承。 时间从时间线开始,经过多个步骤向下传递至每个 动画效果。 在每一步中,时间都可以向后或向前偏移、缩放、 反转、暂停和重复。

计时节点的层次结构
计时节点的层次结构。 树中的每个节点都从其父节点派生时间。

在本规范的这一层级中,该层次结构较浅。 本规范的后续层级将引入组效果的概念, 从而允许更深的计时层次结构。

4.2. 时间值

计时基于计时节点之间时间关系的层次结构。 父节点以时间值的形式 向其子节点提供计时信息。

时间值是一个实数, 名义上表示 从某个时刻起经过的毫秒数。 时间值 与挂钟毫秒之间的联系 可能会由于该值在穿过时间层次结构时 被施加任意数量的变换而变得不明显。

将来可能会有基于滚动位置 或 UI 手势的时间线,在这种情况下,时间值与 毫秒之间的联系将进一步减弱。

时间值也可以是未解析的,例如, 当计时节点所处的状态无法生成时间值时。

4.3. 时间线

时间线为同步目的提供 时间值的来源。

在任意给定时刻,时间线都有一个 当前的时间值,简称为该时间线的当前时间。

时间线可能无法始终 返回有意义的时间 值,而只能返回未解析的时间值。 例如,它可能相对于尚未发生的时刻进行定义, 例如文档的 load 事件触发时刻。 当时间线的时间值为未解析时, 该时间线被认为是非活动的。

如果时间线报告的单调 递增的 当前 时间始终大于或等于 其先前报告的当前时间,则该时间线是单调递增的。

特定类型的时间线可以定义一个过程, 用于针对时间 值 time 将时间线时间转换为 相对于原点的时间,以便比较由基于挂钟的时间线产生的时间 值。

时间线可以与文档关联。

当被要求为 Document doc 在时间戳 now 处更新动画并发送事件时,运行以下步骤:

  1. 更新时间线中所有 与 doc 关联的时间线的当前时间,并将 now 作为时间戳传入。

    由于计时模型具有分层性质,更新时间线的当前时间还 涉及:

  2. 为 doc 移除已替换的动画。

  3. 执行微任务检查点。

    注意: 这是为了确保在上一步更新时间线的过程中, 因解析或拒绝 Promise 对象而加入队列的任何微任务, 都会在分派动画事件之前运行其回调。

  4. 令 events to dispatch 为 doc 的待处理 动画事件 队列的副本。

  5. 清空 doc 的待处理动画事件队列。

  6. 按如下方式对 events to dispatch 中的动画事件执行稳定排序:

    1. 按照事件的预定事件时间对事件排序,使 计划较早发生的事件排在计划较晚发生的事件之前, 而预定事件时间为未解析的事件排在具有 已解析预定事件时间的事件之前。

    2. 对于具有相同预定事件时间的事件,按其合成顺序排序。

    注意: 对事件进行排序的目的是尽可能确保, 即使在性能不同、因而帧率不同的设备上, 事件也能以一致的顺序分派。

    注意: 要求排序必须为稳定排序,是因为有时 多个事件可能以相同的预定事件时间加入队列。例如,持续时间为零的 CSS 动画可能会同时分派 animationstart 和 animationend 事件,并且 应保留这些事件的顺序。

  7. 使用上一步建立的顺序,在各自 对应的目标上分派 events to dispatch 中的每个事件。

通常可以方便地将每次调用此过程描述为 建立一个新的动画 帧。 对动画或动画效果的计时属性进行更改,或 添加和移除这些对象,可能导致计时模型或 动画模型的输出发生变化,但这些操作本身不会创建 新的动画帧, 而只是更新当前的动画 帧。

4.3.1. 文档时间线

文档时间线是一种 时间线,它与文档 关联,并且其当前时间是根据每次运行更新 动画并发送事件过程时提供的 now 时间戳加上固定偏移量计算得到的。 此固定偏移量称为文档时间线的原点时间。

一定有比“原点时间” 更好的术语—— 它与“时间原点”太相似了。 [问题 #2079]

在为其关联文档建立时间原点之前, 文档时间线是 非活动的。

在文档 时间线变为活动后,它是单调 递增的。

与一个并非 活动文档的 Document 关联的文档时间线 也被认为是非活动的。

要针对文档时间线 timeline,将时间线 时间 timeline time 转换为相对于原点的时间, 返回 timeline time 与 timeline 的原点 时间之和。如果 timeline 非活动,则返回一个未解析的时间值。

4.3.2. 默认文档时间线

每个 Document 都有一个称为默认文档时间线的文档 时间线。 默认文档时间线对于每个文档都是唯一的, 并且在文档的整个生命周期内持续存在,包括调用document.open() [HTML] 时。

默认文档时间线的原点时间为零。

本节为非规范性内容

由于不会对提供给文档 时间线的 now 时间戳值应用任何缩放,因此其产生的时间 值将 与挂钟毫秒成比例。

此外,由于默认文档 时间线的时间 值相对于时间原点的偏移量为零, document.timeline.currentTime 将大致对应于 Performance.now() [HR-TIME],不同之处在于 document.timeline.currentTime 在两次调用 更新动画并发送事件过程之间不会发生变化。

4.4. 动画

本节为非规范性内容

时间线的子项称为 动画。 动画获取一个动画效果,该效果是对某种计时行为的静态 描述,并将其绑定到时间线,使其运行。 动画还通过提供暂停、 定位和速度控制,允许在运行时控制动画效果与其时间线之间的连接。 动画与动画 效果之间的关系 类似于 DVD 播放器与 DVD 之间的关系。

动画将单个 动画 效果(称为其关联效果)连接到时间线,并 提供播放控制。 这两个关联都是可选且可配置的,因此 动画在给定时刻可以没有 关联效果或时间线。

动画的用于计时的文档是其时间线所关联的 Document。 如果动画未与时间线关联,或者其时间线未 与文档关联,那么它就没有用于计时的文档。

动画的开始时间是其关联效果 计划开始播放时,其时间线的时间值。 动画的开始时间初始为未解析。

动画还 维护一个保持时间时间值,它用于在暂停等情况下固定动画的输出时间值,该输出值称为其当前时间。 保持时间 初始为未解析。

为了确定相互冲突的动画的相对顺序, 动画会按照其创建顺序附加到全局动画列表中。然而,某些动画 类别可以提供其他动画排序方式 (参见§ 5.4.1 动画类别)。

4.4.1. 设置动画的时间线

将动画 animation 的时间线设置为可以为 null 的 new timeline 的 设置动画的时间线过程如下:

  1. 令 old timeline 为 animation 当前的时间线(如果有)。

  2. 如果 new timeline 与 old timeline 是同一个对象, 则中止此过程。

  3. 令 animation 的时间线为 new timeline。

  4. 如果 animation 的开始 时间是已解析的,则使 animation 的保持时间变为未解析。

    注意: 此步骤确保 animation 的已完成播放状态 不会“粘滞”,而是根据其更新后的当前时间重新求值。

  5. 针对 animation 运行更新动画的完成状态过程,并将 did seek 标志设为 false,将 synchronously notify 标志设为 false。

4.4.2. 设置动画的关联效果

将动画 animation 的关联效果设置为可以为 null 的 new effect 的 设置动画的关联效果过程如下:

  1. 令 old effect 为 animation 当前的关联效果(如果有)。

  2. 如果 new effect 与 old effect 是同一个对象, 则中止此过程。

  3. 如果 animation 有待处理暂停任务,则重新调度该 任务,使其在 animation 就绪后尽快运行。

  4. 如果 animation 有待处理播放任务,则重新调度该任务, 使其在 animation 就绪以播放 new effect 后尽快运行。

  5. 如果 new effect 不是 null,并且 new effect 是另一个动画 previous animation 的关联效果, 则针对 previous animation 运行设置 动画的关联效果过程(即此过程),并将 null 作为 new effect 传入。

  6. 令 animation 的关联效果为 new effect。

  7. 针对 animation 运行更新动画的完成状态过程,并将 did seek 标志设为 false,将 synchronously notify 标志设为 false。

4.4.3. 动画的当前时间

动画会向其关联 效果提供一个时间值, 称为动画的当前时间。

当前 时间根据以下第一个匹配的条件计算:

如果动画的保持时间是已解析的,

当前时间为动画的保持 时间。

如果以下任一条件为真:

  1. 动画没有关联的时间线,或者

  2. 关联的时间线是非活动的,或者

  3. 动画的开始时间是未解析的。

当前时间是一个未解析的时间值。

否则,
当前时间 = (timeline time - 开始时间) × 播放速率

其中 timeline time 是关联时间线的当前时间值。 播放速率 值在§ 4.4.15 速度控制中定义。

4.4.4. 设置动画的当前时间

可以将动画的当前 时间设置为新值,以定位动画。 设置当前时间的过程分为两部分定义。

将动画 animation 的当前时间静默设置为 seek time 的 静默设置当前时间过程如下:

  1. 如果 seek time 是一个未解析的时间值, 则执行以下步骤。

    1. 如果当前时间是已解析的, 则抛出一个 TypeError。

    2. 中止这些步骤。

  2. 按如下方式更新 animation 的保持时间或开始时间:

    如果以下任一条件为真:

    将 animation 的保持时间设置为 seek time。

    否则,

    将 animation 的开始时间设置为计算 timeline time - (seek time / 播放速率) 所得到的结果,其中 timeline time 是与 animation 关联的时间线的当前时间值。

  3. 如果 animation 没有关联的时间线,或者关联的时间线是非活动的, 则使 animation 的开始时间变为未解析。

    这会保持以下不变量:当不存在活动时间线时, 只能设置开始时间和动画的当前时间中的一个。

  4. 使 animation 的先前当前时间变为未解析。

将动画 animation 的当前时间设置为 seek time 的设置当前时间过程如下:

  1. 运行将 animation 的当前时间静默设置为 seek time 的步骤。

  2. 如果 animation 有待处理暂停任务,则通过执行以下步骤同步 完成暂停操作:

    1. 将 animation 的保持时间设置为 seek time。

    2. 对 animation 应用任何待处理的播放速率。

    3. 使 animation 的开始时间变为未解析。

    4. 取消待处理暂停任务。

    5. 使用 animation 兑现 animation 的当前就绪 promise。

  3. 针对 animation 运行更新动画的完成状态过程,并将 did seek 标志设为 true,将 synchronously notify 标志设为 false。

4.4.5. 设置动画的开始时间

将动画 animation 的开始 时间设置为 new start time 的过程如下:

  1. 令 timeline time 为 animation 所关联时间线的当前时间值。 如果没有与 animation 关联的时间线,或者 关联的时间线是非活动的, 则令 timeline time 为未解析。

  2. 如果 timeline time 是未解析的,而 new start time 是已解析的,则使 animation 的保持时间 变为未解析。

    这会保持以下不变量:当不存在活动时间线时, 只能设置开始时间和动画的当前时间中的一个。

  3. 令 previous current time 为 animation 的当前时间。

    注意: 这是应用上一步中的更改之后的当前时间, 这些更改可能会导致当前时间变为未解析。

  4. 对 animation 应用任何待处理的播放速率。

  5. 将 animation 的开始时间设置为 new start time。

  6. 根据以下第一个匹配的条件更新 animation 的保持时间,

    如果 new start time 是已解析的,

    如果 animation 的播放速率不为零, 则使 animation 的保持时间变为未解析。

    否则(new start time 是未解析的),

    将 animation 的保持时间设置为 previous current time,即使 previous current time 是未解析的。

  7. 如果 animation 有待处理播放任务或 待处理暂停 任务,则取消该任务,并使用 animation 兑现 animation 的当前就绪 promise。

  8. 针对 animation 运行更新动画的完成状态过程,并将 did seek 标志设为 true,将 synchronously notify 标志设为 false。

4.4.6. 等待关联效果

本节为非规范性内容

动画执行的某些操作可能不会 即时发生。 例如,某些用户代理可能会将动画的播放委托给 单独的进程或专用图形硬件, 其中每一种方式都可能产生一定的设置开销。

如果此类动画从触发动画的时刻开始计时, 则动画的第一帧与第二帧之间可能会发生显著跳跃, 与所涉及的设置时间相对应。

为了避免此问题,Web 动画通常从动画第一帧 完成的时刻开始对动画计时。 这通过动画上一个未解析的开始时间来表示,该开始时间会在动画就绪时 变为已解析。 内容可以通过将开始 时间设置为一个已解析的时间值来选择退出此行为。

当以下两个条件首次同时为真时,动画即为就绪:

4.4.7. 当前就绪 promise

每个动画都有一个 当前就绪 promise。 当前就绪 promise最初是一个已兑现的Promise,它使用创建新的已兑现 Promise过程创建, 以动画本身作为其值,并在动画的相关 Realm中创建。

每当动画在之前没有待处理任务的情况下 将待处理播放 任务或待处理暂停任务加入队列,或者动画被取消时(参见§ 4.4.14 取消动画), 该对象都会被一个新的Promise 对象替换。

请注意,由于待处理播放请求和 待处理暂停请求使用的是同一个对象,因此建议作者在Promise 对象被兑现时检查 动画的状态。

例如,在以下代码片段中,当当前就绪 promise被兑现时, 动画的状态将为正在运行。 这是因为 play 操作发生时,一个待处理 播放任务仍在队列中,因此会复用当前就绪 promise。

animation.pause();
animation.ready.then(function() {
  // 显示 'running'
  alert(animation.playState);
});
animation.play();

4.4.8. 播放动画

给定标志 auto-rewind,针对 animation 执行播放 动画的过程如下:

  1. 令 aborted pause 为一个布尔标志,如果 animation 有待处理暂停 任务则为 true,否则为 false。

  2. 令 has pending ready promise 为一个初始值为 false 的布尔标志。

  3. 令 seek time 为一个初始为未解析的时间值。

  4. 如果 auto-rewind 标志为 true,则执行以下第一个 匹配条件所对应的步骤(如果有):

    如果 animation 的有效播放速率 ≥ 0, 并且 animation 的当前时间满足以下任一条件:

    将 seek time 设置为零。

    如果 animation 的有效播放速率 < 0, 并且 animation 的当前时间满足以下任一条件:

    如果关联效果结束时间为正 无穷大,

    抛出一个“InvalidStateError” DOMException, 并 中止这些步骤。

    否则,

    将 seek time 设置为 animation 的关联效果结束时间。

  5. 如果以下三个条件全部满足:

    则将 seek time 设置为零。

    注意: 上述步骤确保此过程无论 auto-rewind 标志如何设置, 都会播放空闲动画。

  6. 如果 animation 有一个并非单调递增的关联时间线, 则令 has finite timeline 为 true。

  7. 如果 seek time 是已解析的,

    如果 has finite timeline 为 true,
    1. 将 animation 的开始时间设置为 seek time。

    2. 令 animation 的保持时间为未解析。

    3. 对 animation 应用任何待处理的播放 速率。

    否则,

    将 animation 的保持时间设置为 seek time。

  8. 如果 animation 的保持时间是已解析的, 则令其开始时间为未解析。

  9. 如果 animation 有待处理播放任务或待处理暂停任务,

    1. 取消该任务。

    2. 将 has pending ready promise 设置为 true。

  10. 如果以下四个条件全部满足:

    则中止此过程。

  11. 如果 has pending ready promise 为 false, 则令 animation 的当前就绪 promise为 animation 的相关 Realm中的新 promise。

  12. 调度一个任务,使其在 animation 就绪后尽快运行。 该任务应执行以下步骤:

    1. 断言 animation 的开始时间或保持 时间中至少有一个是已解析的。

    2. 令 ready time 为 animation 变为就绪时, 与 animation 关联的时间线的时间值。

    3. 执行以下第一个匹配条件所对应的步骤(如果有):

      如果 animation 的保持时间是 已解析的,
      1. 对 animation 应用任何待处理的 播放速率。

      2. 令 new start time 为针对 animation 计算 ready time - 保持时间 / 播放速率 所得到的结果。 如果播放速率为零,则令 new start time 直接为 ready time。

      3. 将 animation 的开始时间设置为 new start time。

      4. 如果 animation 的播放速率不为 0,则使 animation 的保持 时间变为未解析。

      如果 animation 的开始时间 是已解析的,并且 animation 有 待处理播放速率,
      1. 令 current time to match 为针对 animation 计算 (ready time - 开始时间) × 播放速率 所得到的结果。

      2. 对 animation 应用任何待处理的 播放速率。

      3. 如果 animation 的播放速率为零,则令 animation 的保持 时间为 current time to match。

      4. 令 new start time 为针对 animation 计算 ready time - current time to match / 播放速率 所得到的结果。 如果播放速率为零,则令 new start time 直接为 ready time。

      5. 将 animation 的开始时间设置为 new start time。

    4. 使用 animation 兑现 animation 的当前 就绪 promise。

    5. 针对 animation 运行更新动画的完成 状态过程,并将 did seek 标志设为 false, 将 synchronously notify 标志设为 false。

      请注意,上述两个步骤的顺序非常重要, 因为这意味着具有零长度关联 效果的动画会先兑现其当前就绪 promise,再兑现其当前已完成 promise。

    只要上述任务已被调度但尚未运行,就称 animation 具有待处理 播放任务。 然而,当该任务正在运行时,animation 不具有 待处理播放 任务。

    如果用户代理确定 animation 已立即就绪,则它 可以将上述任务调度为微任务,使其在下一个微任务检查点运行,但不得 同步执行该任务。

    上述要求异步运行待处理播放任务, 可确保如下代码在不同实现之间表现一致:

    animation.play();
    animation.ready.then(
      () => { console.log('播放已开始'); },
      () => { console.log('播放已取消'); }
    );
    // 假设某个条件要求取消播放……
    animation.cancel();
    // “播放已取消”将被打印到控制台。
    

    在上述代码中,如果待处理播放任务同步运行,则当前就绪 promise将不会被拒绝。

  13. 针对 animation 运行更新动画的完成状态过程,并将 did seek 标志设为 false,将 synchronously notify 标志设为 false。

4.4.9. 暂停动画

每当动画 具有一个未解析的开始时间时, 其当前 时间将被暂停。

与播放 动画一样,暂停可能不会 即时发生(参见§ 4.4.6 等待关联 效果)。 例如,如果动画由单独的进程执行,则可能 有必要同步当前时间,以确保它 反映动画进程所绘制的状态。

针对 animation 执行暂停 动画的过程如下:

  1. 如果 animation 有待处理暂停任务,则中止这些步骤。

  2. 如果 animation 的播放 状态为已暂停, 则中止这些步骤。

  3. 令 seek time 为一个初始为未解析的时间值。

  4. 如果 animation 有一个并非单调递增的关联时间线, 则令 has finite timeline 为 true。

  5. 如果 animation 的当前时间是未解析的, 则根据以下第一个匹配的条件执行步骤:

    如果 animation 的播放速率 ≥ 0,

    将 seek time 设置为零。

    否则,
    如果 animation 的关联效果结束时间为正 无穷大,

    抛出一个“InvalidStateError” DOMException, 并中止这些步骤。

    否则,

    将 seek time 设置为 animation 的关联效果 结束时间。

  6. 如果 seek time 是已解析的,

    如果 has finite timeline 为 true,

    将 animation 的开始时间设置为 seek time。

    否则,

    将 animation 的保持时间设置为 seek time。

  7. 令 has pending ready promise 为一个初始值为 false 的布尔标志。

  8. 如果 animation 有待处理播放任务,则取消该任务, 并令 has pending ready promise 为 true。

  9. 如果 has pending ready promise 为 false, 则将 animation 的当前就绪 promise设置为 animation 的相关 Realm中的新 promise。

  10. 调度一个任务,使其在以下两个条件首次同时为真时执行:

    • 用户代理已执行暂停播放 animation 的关联 效果(如果有)所需的任何处理。

    • 动画与一个并非非活动的时间线相关联。

    该任务应执行以下步骤:

    1. 令 ready time 为用户代理完成暂停播放 animation 的关联效果所需处理时, 与 animation 关联的时间线的时间值。

    2. 如果 animation 的开始时间是已解析的,而其保持 时间不是已解析的, 则令 animation 的保持时间为计算 (ready time - 开始时间) × 播放速率 所得到的结果。

      注意: 如果动画 已完成, 或者动画有待处理播放任务, 则保持时间可能已经被设置。 无论哪种情况,我们都希望在进入已暂停状态时保留该保持 时间。

    3. 对 animation 应用任何待处理的播放速率。

    4. 使 animation 的开始时间变为未解析。

    5. 使用 animation 兑现 animation 的当前 就绪 promise。

    6. 针对 animation 运行更新动画的完成 状态过程,并将 did seek 标志设为 false, 将 synchronously notify 标志设为 false。

    只要上述任务已被调度但尚未运行,就称 animation 具有待处理 暂停任务。 然而,当该任务正在运行时,animation 不具有待处理暂停 任务。

    与待处理播放任务一样,用户代理必须异步运行待处理 暂停任务,尽管它最早可以在下一个微任务检查点运行。

  11. 针对 animation 运行更新动画的完成状态过程,并将 did seek 标志设为 false, 将 synchronously notify 标志设为 false。

4.4.10. 到达末尾

本节为非规范性内容

DVD 播放器或磁带播放器通常会持续播放,直到到达 媒体末尾,此时便会停止。 如果此类播放器能够反向播放,则通常会在 到达媒体开头时停止播放。 为了模拟这种行为,并与 HTML 的媒体元素 [HTML] 保持一致,Web 动画中的动画的当前时间 不会向前播放超过其关联效果的结束 时间,也不会向后播放超过时间零。

到达其播放范围自然边界的动画称为已完成。

从图形上看,限制当前时间的效果如下所示。

限制动画当前时间的效果。
对一个开始时间为 1s、关联效果长度为 3s、且 播放 速率为正的动画限制其当前时间的效果。 动画的当前时间到达关联效果的末尾后, 将被限制在 3s。

不过,可以将动画的当前时间定位到 超过关联效果结束时间的时刻。 这样做时,当前时间不会继续前进,但 动画的行为会如同在所定位的时间暂停一样。

例如,这允许将一个没有关联效果的动画的当前时间 定位到 5s。 如果之后将一个结束时间晚于 5s 的关联效果 与该动画关联,则播放将从 5s 标记处开始。

当动画的关联效果长度发生变化时, 也可能出现与上述场景类似的行为。

类似地,当播放 速率为负时,当前时间不会越过时间零继续前进。

4.4.11. 当前已完成 promise

每个动画都有一个当前已完成 promise。 当前 已完成 promise最初是一个待处理的Promise 对象。

每当动画离开已完成播放状态时, 该对象都会被一个新的promise替换。

4.4.12. 更新完成状态

对于播放速率为正的动画, 当前 时间会持续增加, 直到到达关联效果结束时间。

动画的关联效果 结束时间等于动画关联效果的结束 时间。 如果动画没有关联效果,则关联效果 结束时间为零。

对于播放速率为负的动画, 当前 时间会持续减少, 直到到达零。

已到达此边界(或越过此边界)并且具有已解析开始时间的正在运行动画被称为已完成。

每次修改动画对象时,都会使用下面定义的更新动画的完成 状态过程检查是否跨越了此边界。该过程也作为更新 动画并发送事件过程的一部分运行。在这两种情况下,下面定义的 did seek 标志都 设置为 false。

对于每个动画,用户代理都会维护一个先前当前 时间时间值, 其初始为未解析。

尽管在正常播放期间, 动画的当前 时间被限制在上述边界内, 但可以使用动画的设置当前时间过程, 将动画的当前时间定位到这些边界之外的时刻。

给定标志 did seek(用于指示更新是否在设置 当前时间之后执行)以及标志 synchronously notify(用于指示 此更新是在我们期望已完成事件入队和已完成 promise 兑现 会立即发生(如果会发生)的上下文中调用),针对 animation 执行更新动画的完成状态的过程如下:

  1. 如果 did seek 为 false,则令 unconstrained current time 为 通过计算当前时间得到的结果,其中使用一个未解析的时间值替代保持 时间。 如果 did seek 为 true,则 unconstrained current time 等于当前时间。

    注意: 这是为了适应可能改变 方向的时间线。 如果没有此定义,曾经完成的动画即使在 其时间线沿相反方向推进时仍会保持完成状态。

  2. 如果以下三个条件全部为真,

    则根据以下针对 animation 的第一个匹配条件(如果有),更新 animation 的保持时间:

    如果播放速率 > 0,且 unconstrained current time 大于或等于关联效果 结束时间,

    如果 did seek 为 true,则令保持时间为 unconstrained current time 的值。

    如果 did seek 为 false,则令保持时间为先前当前时间和关联效果结束时间中的最大 值。 如果先前当前时间是未解析的, 则令保持时间为关联 效果结束时间。

    如果播放速率 < 0,且 unconstrained current time 小于或等于 0,

    如果 did seek 为 true,则令保持时间为 unconstrained current time 的值。

    如果 did seek 为 false,则令保持时间为先前当前时间和零中的最小值。 如果先前当前时间是未解析的, 则令保持时间为零。

    如果播放速率 ≠ 0,并且 animation 与一个活动时间线相关联,

    执行以下步骤:

    1. 如果 did seek 为 true,并且保持时间 是已解析的,则令 animation 的开始时间等于计算 timeline time - (保持时间 / 播放速率) 所得到的结果,其中 timeline time 是与 animation 关联的时间线的当前时间值。

    2. 令保持时间为未解析。

  3. 将 animation 的先前当前时间设置为计算其当前时间所得到的结果。

  4. 如果 animation 的播放 状态为已完成,则令 current finished state 为 true。 否则,令其为 false。

  5. 如果 current finished state 为 true,并且当前已完成 promise尚未兑现,则执行以下步骤:

    1. 令完成通知步骤指代以下 过程:

      1. 如果 animation 的播放状态不等于已完成, 则中止这些步骤。

      2. 使用 animation 兑现 animation 的当前 已完成 promise对象。

      3. 创建一个 AnimationPlaybackEvent, finishEvent。

      4. 将 finishEvent 的 type 属性设置为finish。

      5. 将 finishEvent 的 currentTime 属性 设置为 animation 的当前时间。

      6. 将 finishEvent 的 timelineTime 属性设置为与 animation 关联的时间线的当前时间。 如果 animation 未与时间线关联,或者该时间线 是非活动的,则令 timelineTime 为 null。

      7. 如果 animation 有用于计时的文档,则将 finishEvent 与其目标 animation 一同附加到其用于计时的文档的待处理动画 事件队列。 对于预定事件时间,使用将 animation 的关联效果结束时间转换 为相对于原点的时间所得的结果。

        否则,将一个任务加入队列,以在 animation 上分派 finishEvent。 此任务的任务源是DOM 操作任务 源。

    2. 如果 synchronously notify 为 true,则取消任何已排队用于运行此 animation 的完成通知步骤的微任务, 并立即运行完成 通知 步骤。

      否则,如果 synchronously notify 为 false,则将 一个微任务加入队列,以运行 animation 的完成通知步骤, 除非已经有一个微任务排队以运行 animation 的这些步骤。

  6. 如果 current finished state 为 false,并且 animation 的当前已完成 promise已经 兑现,则将 animation 的当前已完成 promise设置为 animation 的相关 Realm中的新 promise。

通常,关于动画完成状态的通知是 异步执行的。这允许动画暂时 进入已完成 播放状态,而不会 触发事件或兑现 promise。

例如,在以下代码片段中,animation 会暂时 进入已完成状态。如果完成状态的通知 同步发生,则此代码会导致finish 事件加入队列, 并兑现当前已完成 promise。但是,如果我们 反转两条语句的顺序,使 iterations 先更新,则 不会发生这种情况。 为避免这种令人意外的行为,关于动画完成状态的通知 通常会异步执行。

var animation = elem.animate({ left: '100px' }, 2000);
animation.playbackRate = 2;
animation.currentTime = 1000; // animation 现在已完成
animation.effect.updateTiming({ iterations: 2 }); // animation 不再处于已完成状态

这种异步行为的一个例外是执行完成 动画过程时(通常通过调用 finish() 方法)。在这种情况下,作者完成 动画的意图很明确,因此关于动画完成状态的通知 会同步发生,如下所示。

var animation = elem.animate({ left: '100px' }, 1000);
animation.finish(); // finish 事件立即加入队列,并且已完成 promise
                    // 会被兑现,尽管以下语句
                    // 会导致动画离开已完成状态
animation.currentTime = 0;

请注意,与完成动画过程一样, 取消动画过程同样会以同步方式将cancel 事件加入队列,并拒绝 当前 已完成 promise和当前就绪 promise。

4.4.13. 完成动画

可以通过下面定义的针对 animation 的完成动画过程, 将动画推进到其当前播放方向的自然末尾:

  1. 如果 animation 的有效播放速率为零, 或者 animation 的有效播放 速率 > 0 且关联效果结束时间为无穷大,则抛出一个“InvalidStateError” DOMException, 并 中止这些步骤。

  2. 对 animation 应用任何待处理的播放速率。

  3. 按如下方式设置 limit:

    如果播放速率 > 0,

    令 limit 为关联效果结束时间。

    否则,

    令 limit 为零。

  4. 静默设置当前时间为 limit。

  5. 如果 animation 的开始时间是未解析的,并且 animation 有一个关联的活动时间线,则令开始时间为计算 timeline time - (limit / 播放速率) 所得到的结果,其中 timeline time 是关联时间线的当前时间 值。

  6. 如果存在待处理暂停任务,并且开始时间是已解析的,

    1. 令保持时间为未解析。

      通常保持时间已经是 未解析的,唯一的例外是动画之前处于空闲状态时。
    2. 取消待处理暂停任务。

    3. 使用 animation 兑现 animation 的当前就绪 promise。

  7. 如果存在待处理播放任务,并且开始时间是已解析的,则取消该任务, 并使用 animation 兑现 animation 的当前就绪 promise。

  8. 针对 animation 运行更新动画的完成状态过程,并将 did seek 标志设为 true, 将 synchronously notify 标志设为 true。

4.4.14. 取消动画

可以取消动画,这会使当前时间 变为未解析,从而移除 关联效果所产生的任何效果。

针对 animation 执行取消 动画的过程 如下:

  1. 如果 animation 的播放状态不是空闲,则执行以下步骤:

    1. 针对 animation 运行重置动画的待处理 任务过程。

    2. 使用名为 "AbortError" 的 DOMException 拒绝当前已完成 promise。

    3. 将当前已完成 promise的 [[PromiseIsHandled]] 内部槽设置为 true。

    4. 令当前已完成 promise为 animation 的相关 Realm中的新 promise。

    5. 创建一个 AnimationPlaybackEvent, cancelEvent。

    6. 将 cancelEvent 的 type 属性设置为cancel。

    7. 将 cancelEvent 的 currentTime 设置为 null。

    8. 令 timeline time 为与 animation 关联的时间线的当前时间。 如果 animation 未与活动时间线关联,则令 timeline time 为一个未解析的时间值。

    9. 将 cancelEvent 的 timelineTime 设置为 timeline time。如果 timeline time 是未解析的,则将其设置为 null。

    10. 如果 animation 有用于计时的文档,则将 cancelEvent 与其目标 animation 一同附加到其用于 计时的文档的待处理动画 事件队列。 如果 animation 与一个定义了将时间线时间转换为 相对于原点的 时间过程的活动 时间线关联,则令预定事件时间为将该过程应用于 timeline time 所得到的结果。 否则,预定事件时间是一个未解析的时间 值。

      否则,将一个任务加入队列,以在 animation 上分派 cancelEvent。 此任务的任务源是DOM 操作任务 源。

  2. 使 animation 的保持时间变为未解析。

  3. 使 animation 的开始时间变为未解析。

针对 animation 执行重置动画的待处理任务的过程如下:

  1. 如果 animation 没有待处理播放任务或待处理暂停任务,则中止此 过程。

  2. 如果 animation 有待处理播放任务,则取消该任务。

  3. 如果 animation 有待处理暂停任务,则取消该任务。

  4. 对 animation 应用任何待处理的播放速率。

  5. 使用名为 "AbortError" 的 DOMException 拒绝 animation 的当前就绪 promise。

  6. 将 animation 的当前就绪 promise的 [[PromiseIsHandled]] 内部槽设置为 true。

  7. 令 animation 的当前就绪 promise为在 animation 的相关 Realm中,以值 animation 创建新的已兑现 Promise 对象所得的结果。

4.4.15. 速度控制

可以通过设置动画的播放速率来控制其播放速度。 例如,将播放速率设置为 2 会使动画的当前时间以其时间线两倍的速率增加。 类似地,将播放速率设置为 -1 会使 动画的当前时间以与其时间 值从 时间线增加相同的速率减少。

动画具有播放速率,它 提供一个缩放因子,用于将关联时间线的时间值的变化速率转换为动画当前时间的变化速率。 播放速率的 初始值为 1。

将动画的播放 速率设置为零实际上会暂停动画(但是,播放状态并不一定会 变为已暂停)。

4.4.15.1. 设置动画的播放速率

将 动画 animation 的播放 速率设置为 new playback rate 的过程如下:

  1. 清除 animation 上的任何待处理播放速率。

  2. 令 previous time 为更改播放速率之前 animation 的当前时间的值。

  3. 令 previous playback rate 为 animation 当前的有效播放 速率。

  4. 将播放速率 设置为 new playback rate。

  5. 执行以下第一个匹配条件 所对应的步骤(如果有):

    如果 animation 与一条单调递增的时间线关联,并且 previous time 是已解析的,

    将 animation 的当前时间设置为 previous time。

    如果 animation 与一条非 null 的时间线关联,且该时间线并非单调递增, animation 的开始时间是已解析的,关联效果结束时间不是无穷大,并且满足以下任一条件:

    • previous playback rate < 0 且 new playback rate ≥ 0,或者

    • previous playback rate ≥ 0 且 new playback rate < 0,

    将 animation 的开始时间设置为针对 animation 计算 关联效果结束时间 - 开始时间 所得到的结果。

    注意: 这实际上会在非单调时间线上翻转动画的 开始/结束时间, 同时保留开始时间相对于另一方向的相对偏移。

4.4.15.2. 无缝更新动画的播放速率

对于正在另一个进程或线程中运行的进行中动画, 如果运行动画的进程或线程当前未与 执行更新的进程或线程同步,则设置 播放速率过程可能导致动画发生跳变。

为了使动画的播放速率发生无缝变化,动画可以具有一个待处理播放速率, 它定义在完成任何必要的同步之后应用的播放速率 (适用于动画在不同线程或 进程中运行的情况)。

动画的待处理播放速率初始为未设置。

animation 的有效 播放速率是其待处理 播放速率(如果已设置), 否则为动画的播放速率。

当要对一个动画 animation 应用任何 待处理播放 速率时,执行以下步骤:

  1. 如果 animation 没有待处理播放速率,则中止这些步骤。

  2. 将 animation 的播放速率设置为其待处理播放速率。

  3. 清除 animation 的待处理播放速率。

将动画 animation 的播放速率无缝更新为 new playback rate,同时保留其当前时间的过程如下:

  1. 令 previous play state 为 animation 的播放状态。

    注意: 有必要在更新 animation 的有效播放速率之前记录播放状态,因为 在以下逻辑中, 如果 animation 当前为已完成,我们希望立即应用 animation 的待处理播放速率,而不论应用待处理播放速率后 它是否仍然处于已完成状态。

  2. 令 animation 的待处理播放速率为 new playback rate。

  3. 执行以下第一个匹配条件所对应的步骤:

    如果 animation 有待处理播放任务或待处理暂停任务,

    中止这些步骤。

    注意: 不同类型的待处理任务在运行时会 应用待处理 播放速率,因此在 这种情况下无需进一步操作。

    如果 previous play state 为空闲或已暂停,或者 animation 的当前时间是未解析的,

    对 animation 应用任何待处理的播放速率。

    注意: 上述第二个条件是必要的, 这样,如果我们有一个当前时间未解析且 没有待处理播放任务的正在运行的动画,就不会尝试在下面播放它。

    如果 previous play state 为已完成,
    1. 令 unconstrained current time 为计算 animation 的当前时间所得的结果, 其中使用一个未解析的时间值替代保持时间。

    2. 令 animation 的开始时间为 计算以下表达式所得的结果:

      timeline time - (unconstrained current time / 待处理播放速率)

      其中 timeline time 是与 animation 关联的时间线的当前时间值。

      如果待处理播放速率为零,则令 animation 的开始时间为 timeline time。

    3. 对 animation 应用任何待处理的播放 速率。

    4. 针对 animation 运行更新动画的 完成状态过程,并将 did seek 标志设为 false, 将 synchronously notify 标志设为 false。

    否则,

    针对 animation 运行播放动画过程,并将 auto-rewind 标志设为 false。

4.4.16. 反向播放动画

反向播放动画 animation 的反向播放 动画过程如下:

  1. 如果没有与 animation 关联的时间线, 或者关联的时间线是非活动的,则抛出一个“InvalidStateError” DOMException, 并 中止这些步骤。

  2. 令 original pending playback rate 为 animation 的待处理播放 速率。

  3. 令 animation 的待处理播放速率为其有效播放速率的加法逆元(即 -有效播放速率)。

  4. 针对 animation 运行播放动画的步骤,并将 auto-rewind 标志设为 true。

    如果播放动画的步骤抛出异常,则将 animation 的待处理播放速率设置为 original pending playback rate,并传播该异常。

4.4.17. 播放状态

动画可被描述为处于以下 播放状态之一, 并且每种状态也提供了非规范性描述:

空闲

动画的当前时间是未解析的,并且 动画的开始时间是未解析的,并且 没有待处理任务。 在此状态下,动画没有任何效果。

正在运行

动画具有一个已解析的当前时间,它会在每个动画 帧发生变化(前提是播放速率不为零, 且时间线是活动的 并且单调递增)。

已暂停

动画已暂停,并且当前时间不再变化。

已完成

动画已到达其播放范围的自然边界, 并且当前时间不再更新。

动画 animation 在给定时刻的播放状态 是以下第一个匹配条件 所对应的状态:

以下所有条件均为真:

→ 空闲

以下任一条件为真:

→ 已暂停

对于 animation,当前时间是已解析的,并且以下条件中的任一 为真:

→ 已完成

否则,

→ 正在运行

请注意,已暂停 播放状态实际上 “优先于”已完成播放状态。

但是,可以通过如下方式设置开始时间,将一个在其自然播放范围之外暂停的动画 从已暂停动画 转换为已完成动画, 而无需重新启动:

animation.effect.updateTiming({ duration: 5000 });
animation.currentTime = 4000;
animation.pause();
animation.ready.then(function() {
  animation.effect.updateTiming({ duration: 3000 });
  alert(animation.playState); // 显示 'paused'
  animation.startTime =
    document.timeline.currentTime - animation.currentTime * animation.playbackRate;
  alert(animation.playState); // 显示 'finished'
});

4.4.18. 动画事件

动画事件 包括本规范中定义的动画播放事件,以及CSS 过渡中的事件 [CSS-TRANSITIONS-1] 和CSS 动画中的事件 [CSS-ANIMATIONS-1]。 未来的规范可以用更多类型的动画 事件扩展此集合。

每个 Document 都维护一个待处理动画事件队列,用于存储动画事件及其对应的事件目标 和预定事件 时间。 预定事件 时间是一个相对于时间原点的时间值, 表示如果动画以无限高频率更新时,该事件理想情况下 本应被分派的时间。 更新动画并发送事件过程使用它按时间顺序 对已排队的动画事件进行排序。 请注意,例如,如果动画的时间线生成的值与时间原点无关(例如 跟踪滚动位置的时间线),或者时间线是非活动的, 则该值可能是未解析的。

4.4.18.1. 动画事件排序

提供以下定义以帮助对已排队的事件进行排序。

要将动画时间转换为 时间线时间,即一个相对于动画 animation 的开始 时间的时间值 time,执行以下步骤:

  1. 如果 time 是未解析的,则返回 time。

  2. 如果 time 是无穷大,则返回一个未解析的时间值。

  3. 如果 animation 的播放速率为零, 则返回一个未解析的时间值。

  4. 如果 animation 的开始时间是未解析的, 则返回一个未解析的时间值。

  5. 返回计算以下表达式所得的结果: time × (1 / playback rate) + start time(其中 playback rate 和 start time 分别为 animation 的播放速率和开始时间)。

要将时间线时间转换为 相对于原点的时间,即一个使用与时间值相同尺度表示的时间值 time,该尺度来自时间线 timeline,执行 以下步骤:

  1. 令 timeline time 为将 time 从动画 时间转换为时间线时间所得的结果。

  2. 如果 timeline time 是未解析的, 则返回 time。

  3. 如果 animation 未与时间线关联, 则返回一个未解析的时间 值。

  4. 如果 animation 与非活动时间线关联, 则返回一个未解析的时间 值。

  5. 如果与 animation 关联的时间线没有将时间线时间转换为 相对于原点的时间的过程, 则返回一个未解析的时间值。

  6. 返回使用为与 animation 关联的时间线定义的过程,将 timeline time 转换为 相对于原点的时间所得的结果。

4.4.18.2. 动画播放事件

当动画播放时, 它们通过动画播放事件报告其状态的变化。

动画 播放事件是计时模型的一个属性。因此, 即使动画的关联效果不存在或没有可观察到的 结果,也会分派这些事件。

4.4.18.3. 动画播放事件的类型
finish

每当动画进入已完成播放状态时加入队列。

cancel

每当动画从另一状态进入空闲播放状态时加入队列。 创建一个初始即为空闲的新的动画不会生成新的cancel 事件。

remove

每当动画被自动移除时加入队列。 参见§ 5.5 替换动画。

4.5. 动画效果

动画效果是 一个指代计时层次结构中某个项目的 抽象术语。

4.5.1. 动画效果 与动画之间的关系

动画的关联效果(如果已设置)是一种动画效果。 动画的关联效果被称为与该动画关联。 在给定时刻,一个动画效果最多只能与一个动画关联。

如果动画效果 effect 与一个 动画关联,而该动画又与 timeline 关联,则称 effect 与 时间线关联,该时间线为 timeline。

4.5.2. 动画效果的类型

本规范定义了一种动画效果:关键帧效果。 本规范的后续层级将定义更多类型的动画效果。

所有类型的动画 效果都定义了一些共同 属性,这些属性将在以下各节中描述。

4.5.3. 活动区间

动画效果计划运行的期间称为其活动 区间。 每个动画效果只有一个这样的区间。

活动区间的下界通常对应于与此动画效果关联的动画的开始 时间,但可能会因动画效果上的开始延迟而发生偏移。

区间的上界由活动 持续时间决定。

开始时间、开始 延迟和活动 持续时间之间的关系如下图所示。

开始延迟对活动区间端点影响的示例
开始延迟对活动 区间端点影响的示例。
(a) 没有延迟的动画效果;开始时间与活动区间的开始重合。
(b) 具有正延迟的动画 效果;活动 区间的开始因该延迟而推迟。
(c) 具有负延迟的动画效果;活动区间的 开始因该延迟而提前。

还可以指定结束延迟, 但它主要仅在对动画进行顺序排列时有用。

动画效果定义 一个活动区间, 即效果计划产生其 效果的时间段,但在活动 区间之外应用的填充模式 除外。

活动 区间的下界由开始延迟定义。

动画效果的开始延迟是一个 有符号偏移量, 相对于与该动画效果关联的动画的开始 时间。

活动 区间的长度称为活动 持续时间,其计算在§ 4.8.2 计算活动持续时间中定义。

与开始延迟类似,动画效果还具有 一个结束延迟,它 主要在根据另一个动画效果的结束时间对动画进行顺序排列时有用。 虽然这通常仅在与本规范后续层级中引入的序列效果结合使用时才有用,但此处将其纳入 是为了表示 SVG 中的 min 属性([SVG11],第 19 章)。

动画效果的结束时间是 计算 max(开始延迟 + 活动 持续时间 + 结束延迟, 0) 所得到的结果。

4.5.4. 局部时间

动画效果在给定 时刻的局部时间 根据以下第一个匹配的条件确定:

如果动画效果与 动画关联,

局部时间为该动画的当前时间。

否则,

局部时间为未解析。

4.5.5. 动画效果的阶段和 状态

本节为非规范性内容

在给定时刻,一个动画效果可能处于三种可能的 阶段之一。 如果一个动画效果具有一个未解析的局部 时间,则它不会处于任何阶段。

不同阶段如下图所示。

用于描述动画效果的不同阶段和状态的示例。
用于描述 动画 效果的不同阶段和状态的示例。

各阶段如下:

前阶段

动画 效果的局部 时间位于效果的活动 区间和结束 时间之前,或者发生在负开始延迟生效的 范围内。

活动阶段

动画 效果的局部 时间位于效果的活动 区间之内,并且位于任何 负开始延迟 或负结束延迟的范围之外。

后阶段

动画 效果的局部 时间位于效果的活动 区间之后,或者如果结束时间更早(由于负结束延迟),则位于结束时间之后,但不位于负开始延迟生效的 范围内。

除了这些阶段之外,一个动画效果还可以被 描述为处于若干相互重叠的状态之一。 这些状态仅在单个动画帧的持续期间建立,主要是为了 方便描述模型中有状态的 部分。

这些状态及其在模型中的用法总结 如下:

播放中

对应于活动时间在每一帧都会变化的动画效果。

当前

对应于一个动画效果,它要么处于 播放中,要么根据其 动画当前的播放速率,将来可能变为播放中。

生效中

对应于具有已解析活动时间的动画效果。 当动画效果处于其 活动阶段,或者处于活动阶段之外、但此时效果的填充 模式(参见§ 4.6 填充行为) 使其活动时间得到解析时,就会发生这种情况。 只有生效中的动画效果才会向其目标应用 结果。

下面给出这些状态各自的规范性定义。

确定一个动画效果的阶段需要以下 定义:

动画 方向

如果该效果与一个 动画关联,并且关联动画的播放速率小于零,则为“向后”; 在所有其他情况下,动画方向为 “向前”。

活动前边界时间

max(min(开始延迟, 结束时间), 0)

活动后边界时间

max(min(开始延迟 + 活动持续时间, 结束时间), 0)

如果一个动画效果的 局部时间不是 未解析的,并且满足以下任一 条件,则该动画效果处于前阶段:

  1. 局部时间小于 活动前边界时间,或者

  2. 动画 方向为“向后”,并且局部 时间等于活动前边界时间。

如果一个动画效果的 局部时间不是 未解析的,并且满足以下任一 条件,则该动画效果处于后阶段:

  1. 局部时间大于 活动后边界 时间,或者

  2. 动画 方向为“向前”,并且局部 时间等于活动后边界时间。

如果一个动画效果的 局部时间 不是未解析的,并且它 既不处于前阶段,也不处于后阶段,则该动画效果处于 活动阶段。

此外,通常可以方便地将一个动画 效果不处于上述任何阶段的情况称为处于空闲阶段。

如果一个动画效果满足以下所有 条件,则它处于播放中:

  1. 该动画 效果处于活动阶段,并且

  2. 该动画 效果与一个动画关联,且该动画并非已完成。

如果以下任一条件为真,则一个动画效果为 当前:

如果一个动画效果的活动 时间(按照§ 4.8.3.1 计算 活动时间中的过程计算) 不是未解析的,则该动画效果处于生效 中。

4.5.6. 相关动画

我们可以根据与一个动画关联的动画效果,将该动画定义为相关。

如果满足以下条件,则一个动画是相关的:

元素或伪元素 target 的相关 动画 是所有动画的集合,其中每个动画至少包含一个动画效果,其效果目标为 target。

元素、伪元素、文档或影子 根——target——的子树的相关动画 是所有动画的集合,其中每个动画至少包含一个动画效果,其效果目标是 target 的包含自身的后代(如果 target 是文档或影子 根,则为后代) 或此类后代的伪元素。

4.6. 填充行为

一个动画 效果在不处于播放中时的效果 由其填充模式决定。

可能的填充模式有:

这些模式的规范性定义已纳入 § 4.8.3.1 计算活动时间中 活动时间的计算。

不建议作者使用填充模式来生成效果 无限期应用的动画。引入填充模式是为了 表示 CSS 动画定义的 animation-fill-mode 属性 [CSS-ANIMATIONS-1]。 但是,它们会产生动画状态无限 累积的情况,从而需要§ 5.5 替换动画中定义的自动移除动画机制。 此外,无限期填充的动画可能导致指定 样式的更改在所有动画完成很久之后仍然无效,因为 动画样式在 CSS 层叠中具有优先权 [css-cascade-3]。

在可能的情况下,作者应优先直接在指定样式中设置动画的最终状态。 这可以通过等待动画完成,然后更新 样式来实现,如下所示:

// 在以下动画完成后的第一帧中,`finished` promise 的回调
// 会在样式更新之前运行,因此不会
// 闪烁。
elem.animate({ transform: 'translateY(100px)' }, 200).finished.then(() => {
  elem.style.transform = 'translateY(100px)';
});

或者,作者可以在动画开始时设置指定样式, 然后从原始值开始进行动画处理, 如下所示:

elem.style.transform = 'translateY(100px)';
elem.animate({ transform: 'none', offset: 0 }, 200);

涉及将许多动画相互叠加的复杂效果可能 需要临时使用 forwards 填充模式,以便在取消动画之前捕获其最终值。 例如:

elem.addEventListener('click', async evt => {
  const animation = elem.animate(
    { transform: `translate(${evt.clientX}px, ${evt.clientY}px)` },
    { duration: 800, fill: 'forwards' }
  );
  await animation.finished;
  // commitStyles 将记录直到并包括 `animation` 的样式,
  // 并使用结果更新 elem 的指定样式。
  animation.commitStyles();
  animation.cancel();
});

4.6.1. 填充模式

本节为非规范性内容

每种填充模式的效果 如下:

none

当动画效果不处于播放中时,它没有任何效果。

forwards

当动画效果处于后阶段时, 动画效果将生成与其计划处于播放中的最后时刻相同的迭代进度值。

在动画效果不处于播放中的所有其他时刻, 它都没有任何效果。

backwards

当动画效果处于前阶段时, 动画效果将生成与其计划开始处于播放中的最早时刻相同的迭代进度值。

在动画效果不处于播放中的所有其他时刻, 它都没有任何效果。

both

当动画效果 处于其前阶段时,使用backwards 填充行为。

当动画效果 处于其后阶段时,使用forwards 填充行为。

下面说明了这些填充模式的一些示例。

各种填充模式及其产生状态的示例。
各种填充模式及其产生状态的示例。
(a) 填充模式 "none"。动画 效果在其活动阶段之外没有效果。
(b) 填充模式 "forwards"。活动阶段 结束后,迭代进度值继续保持 一个填充值。
(c) 填充模式 "backwards"。动画效果会 产生填充值,直到活动阶段开始。
(d) 填充模式 "both"。在活动 阶段之前和之后,动画效果都会产生填充值。

注意: 设置填充模式不会影响活动区间的端点或 各阶段之间的边界。 但是,填充模式确实会影响计时模型的各种其他 属性,因为动画效果的活动时间只在 活动阶段或者应用了填充时才有定义(即,不是未解析的)。

4.7. 重复

4.7.1. 迭代区间

可以指定一个动画效果应重复 固定次数或无限重复。 这种重复发生在活动区间内。 单次重复发生的时间跨度 称为迭代 区间。

与活动 区间不同,一个动画效果可以具有多个迭代区间,尽管通常只有与当前迭代对应的区间 才值得关注。

单次迭代的长度称为迭代 持续时间。 一个动画效果的初始迭代 持续时间为零。

本节为非规范性内容

比较迭代持续时间和活动 持续时间:

迭代持续时间

动画效果的单次迭代 完成所需的时间。

活动持续时间

整个动画效果完成所需的时间, 包括重复。 它可能长于或短于迭代持续时间。

迭代持续时间与活动 持续时间之间的关系如下图所示。

迭代持续时间与活动时间的比较。
一个迭代次数为 2.5 的动画效果的迭代持续时间和活动 持续时间的比较。 请注意,最后一次 迭代的迭代持续时间并未 改变,只是被活动持续时间截断。

4.7.2. 控制迭代

一个动画效果重复的次数称为其迭代次数。 迭代次数是一个 大于或等于 零的实数。 迭代次数也可以是正无穷大,以表示 动画效果无限重复。

除了迭代 次数之外,动画 效果还 具有一个迭代 起点属性,用于指定 动画效果应从迭代序列中的哪个偏移位置开始。 迭代起点是一个 大于或 等于零的有限实数。

这些参数的行为在§ 4.8 核心动画效果计算中的计算中定义。

本节为非规范性内容

迭代 次数和迭代起点参数的效果如下图所示。

迭代次数和迭代起点参数的效果
迭代次数和迭代起点参数的效果。
在 第一种情况下,迭代次数为 2.5,因此 第三次迭代在其迭代 区间进行到一半时被截断。
第二种情况相同,但迭代起点为 0.5。 这会使动画效果从 第一次迭代进行到一半的位置开始。

与迭代 次数参数不同,迭代 起点参数不会影响活动 持续时间的长度。

请注意,迭代起点大于或等于 1 的值通常没有用,除非与一个具有迭代合成 操作为accumulate的动画效果结合使用。

4.7.3. 迭代时间空间

本节为非规范性内容

在 Web 动画中,所有时间都相对于某个参考点。 这些不同的参考点产生不同的时间 空间。

这可以与计算机 图形中使用的坐标空间进行比较。 时间空间的零时刻类似于 坐标空间的原点。

我们可以将重复的动画描述为每次动画重复时 都会建立一个新的时间空间:迭代时间空间。

迭代时间空间是一种时间空间,其零时刻是 动画效果当前迭代的开始。

在 Web 动画模型中,我们还会引用活动 时间,它是相对于活动 区间开始的时间。 然而,此时间空间是模型内部使用的,不会在 编程接口或标记中公开。

这些时间空间如下图所示。

局部时间、活动时间和迭代时间的比较。
对于一个迭代持续时间为 1s、迭代次数为 2.5 的动画,局部时间、活动时间和迭代时间的比较。

注意: 尽管时间空间本身没有边界,Web 动画定义活动时间 和迭代 进度时,会将它们限制在图中所示的设定范围内。 例如,尽管 -1 秒是活动时间空间中的有效时间,但§ 4.8.3.1 计算活动时间中定义的 计算活动时间的过程 永远不会返回负值。

除了这些时间空间之外,我们还可以引用文档时间空间,它是当前全局对象的 Document 的默认文档时间线的时间 值所在的时间 空间。

4.7.4. 区间计时

本节为非规范性内容

当一个动画效果重复时,我们必须定义 迭代边界处的行为。 为此,实际上对于所有区间计时,Web 动画都使用 端点排除的计时模型。 这意味着区间的开始时间 包含在区间内,而结束时间不包含在内。 使用区间表示法可写为 [begin, end)。 当区间重复和 按顺序排列时,此模型可提供合理的行为,因为区间之间不会重叠。

在下面的示例中,对于重复效果,在局部时间 1s 时, 迭代时间为 0。 对于顺序排列的动画,在时间线时间 1s 时,只有动画 B 的关联效果 会处于播放中;不存在重叠。

端点排除计时的图示。
端点排除计时的图示。对于重复和 顺序排列的动画效果,在区间之间的边界处 都不存在重叠。

此行为的一个例外是,在执行填充时,如果填充 从区间端点开始,则会使用该端点。 此行为由§ 4.8.3.3 计算简单迭代 进度中的算法自然产生,并如下图所示。

迭代和填充对迭代时间的影响。
一次迭代后,迭代进度为 0,但两次 迭代之后(以及此后),迭代进度 为 1, 这是由于动画效果进行填充时定义的特殊行为。

4.8. 核心动画效果计算

4.8.1. 概述

本节为非规范性内容

Web 动画计时模型的核心是这样一个过程: 获取一个局部时间值,并 将其转换为迭代进度。

此过程的第一步是计算活动区间的边界,该边界由 活动 持续时间决定。

此过程如下图所示。

活动持续时间的计算。
活动持续时间的计算基于 迭代持续时间乘以迭代次数。

计算活动持续时间的过程在§ 4.8.2 计算活动持续时间中作了规范性定义。

在确定活动持续时间之后,将一个动画效果的局部时间转换为其变换后进度(迭代 进度)的过程如下图所示。

计时模型计算概述。
计时模型计算概述。
(1) 局部时间由关联动画确定。
(2) 将局部时间通过纳入开始 延迟转换为活动时间。
(3) 将活动时间除以迭代 持续时间,同时纳入迭代起点属性,以产生总体 进度。
(4) 然后将总体进度时间转换为 单次迭代内的偏移:简单迭代进度。
(5) 通过纳入播放方向,将简单迭代进度转换为定向 进度。
(6) 最后,将计时函数 应用于定向 进度以产生变换后进度。

第一步,即计算局部时间,在§ 4.5.4 局部 时间中描述。 图中的第 2 至第 4 步在以下各节中描述。 第 5 和第 6 步分别在§ 4.9.1 计算 定向进度和§ 4.10.1 计算 变换后进度中描述。

4.8.2. 计算活动持续时间

活动持续时间按如下方式 计算:

活动持续时间 = 迭代持续时间 × 迭代次数

如果迭代持续时间或迭代次数中任一个为零,则活动持续时间为 零。

此说明是必要的,因为根据 IEEE 754-2008, 无穷大乘以零的结果未定义。

4.8.3. 转换局部时间

4.8.3.1. 计算活动时间

活动时间基于 局部时间和开始延迟。 但是,只有当动画效果应 产生输出时,它才有定义,因此它取决于其填充模式和 阶段,如下所示:

如果动画效果处于前 阶段,

结果取决于以下 第一个匹配的条件:

如果填充 模式为 backwards 或 both,

返回计算 max(局部时间 - 开始延迟, 0) 所得到的结果。

否则,

返回一个未解析的时间值。

如果动画效果处于活动 阶段,

返回计算 局部时间 - 开始延迟 所得到的结果。

如果动画效果处于后阶段,

结果取决于以下 第一个匹配的条件:

如果填充 模式为 forwards 或 both,

返回计算 max(min(局部时间 - 开始延迟, 活动持续时间), 0) 所得到的结果。

否则,

返回一个未解析的时间值。

否则(局部 时间是未解析的),

返回一个未解析的时间值。

4.8.3.2. 计算总体进度

总体进度 描述已经完成的迭代次数 (包括部分迭代),其定义如下:

  1. 如果活动时间是未解析的,则返回未解析。

  2. 根据以下第一个匹配的条件 计算 overall progress 的初始值:

    如果迭代持续时间为零,

    如果动画效果处于前阶段,则令 overall progress 为零,否则令其等于 迭代次数。

    否则,

    令 overall progress 为计算 活动时间 / 迭代持续时间 所得到的结果。

  3. 返回计算 overall progress + 迭代起点 所得到的结果。

4.8.3.3. 计算简单迭代进度

简单 迭代进度是当前迭代进度的一个比例值, 它忽略由播放方向或应用于效果的计时 函数引入的时间变换,并按如下方式计算:

  1. 如果总体 进度是未解析的, 则返回未解析。

  2. 如果总体 进度为无穷大,则令 simple iteration progress 为 迭代起点 % 1.0, 否则,令 simple iteration progress 为 总体进度 % 1.0。

  3. 如果以下所有条件均为真,

    则令 simple iteration progress 为 1.0。

    上述步骤实现了以下行为:当动画的 活动区间恰好在一次迭代的末尾结束时,它会通过 保持最后一次迭代的端点,而不是下一次 迭代的起点来进行填充。

    最后一个条件可防止此规则应用于一开始就从未播放过 任何动画迭代的情况,因为迭代次数为零。

  4. 返回 simple iteration progress。

4.8.4. 计算当前迭代

当前迭代 可以使用以下 步骤计算:

  1. 如果活动时间是未解析的,则返回未解析。

  2. 如果动画效果处于后阶段,并且迭代次数 为无穷大,则返回无穷大。

  3. 如果简单迭代进度为 1.0, 则返回 floor(总体进度) - 1。

  4. 否则,返回 floor(总体进度)。

4.9. 方向控制

动画效果也可以 使用方向控制配置为以 不同方向运行迭代。 为此,动画效果具有一个播放 方向参数,该参数取以下值之一:

这些值的语义已纳入随后 定向进度的计算中。

本节为非规范性内容

这些值的非规范性定义如下:

normal

所有迭代均按指定方式播放。

reverse

所有迭代均以与指定方式 相反的方向播放。

alternate

偶数次迭代按指定方式播放,奇数次迭代以 与指定方式相反的方向播放。

alternate-reverse

偶数次迭代以与指定方式相反的方向 播放,奇数次迭代按指定方式播放。

4.9.1. 计算定向进度

定向进度 根据简单迭代进度使用以下步骤计算:

  1. 如果简单迭代进度是未解析的,则返回未解析。

  2. 使用以下列表中的第一个 匹配条件计算 current direction:

    如果播放方向为 normal,

    令 current direction 为向前。

    如果播放方向为 reverse,

    令 current direction 为反向。

    否则,
    1. 令 d 为当前迭代。

    2. 如果播放方向为 alternate-reverse,则将 d 增加 1。

    3. 如果 d % 2 == 0,则令 current direction 为 向前,否则令 current direction 为反向。 如果 d 为无穷大,则令 current direction 为向前。

  3. 如果 current direction 为向前,则返回 简单迭代进度。

    否则,返回 1.0 - 简单迭代进度。

4.10. 时间变换

通常需要控制一个动画 效果推进的速率。 例如,对动画速率应用缓动可以产生 动量感,并产生更自然的效果。 CSS 缓动函数模块 [CSS-EASING-1] 为此定义了计时 函数。

动画效果各自关联 一个计时函数。 默认计时函数为线性计时 函数。

4.10.1. 计算变换后进度

变换后 进度根据定向进度使用以下步骤计算:

  1. 如果定向 进度是未解析的, 则返回未解析。

  2. 按如下方式计算 before flag 的值:

    1. 使用§ 4.9.1 计算定向 进度中定义的过程确定 current direction。

    2. 如果 current direction 为 forwards, 则令 going forwards 为 true,否则为 false。

    3. 如果动画效果处于前阶段且 going forwards 为 true;或者 动画效果处于后阶段且 going forwards 为 false,则设置 before flag。

  3. 返回对动画效果的计时 函数进行求值所得的结果,其中将定向进度作为输入进度值传入,并将 before flag 作为before 标志传入。

4.11. 迭代进度

动画效果的迭代 进度就是 其变换后 进度。

5. 动画模型

本节为非规范性内容

对于某些类型的动画效果,Web 动画动画 模型获取计时模型产生的迭代进度和当前迭代值,并使用它们计算 相应的输出。

然后,每个此类动画效果的输出会先使用效果 栈与其他动画效果的输出组合,再 应用于目标属性(参见§ 5.4 组合效果)。

5.1. 引言

一个动画效果具有 零个或多个关联属性,它会 响应其计时输出的变化而影响这些属性。这些属性 称为该效果的目标 属性。

给定一个迭代 进度、一个当前迭代和一个底层值,一个动画效果会通过 应用适用于该属性的动画类型中的过程,为每个可动画的目标属性产生 一个效果值。

5.2. 对属性进行动画处理

除非另有规定,所有 CSS 属性都是可动画的。 属性值如何组合由 每个属性的属性定义表中的动画类型行 定义:

不可动画
该属性不可动画。 当它列在动画关键帧中时不会被处理, 并且不受过渡影响。

注意: 属性通常会被排除在动画之外, 因为对其进行动画处理会造成过度复杂的情况。 例如,定义动画参数的属性是不可动画的,因为对其进行动画处理会 产生复杂的递归行为。

注意: 一个仅以 不可 动画属性为目标的动画效果仍会表现出 动画效果的通常行为,例如 触发事件以及 延迟兑现动画的当前已完成 promise。

离散
该属性的值无法有意义地组合, 因此它是不可相加的,并且插值会在 50%(p=0.5)处 从 Va 切换到 Vb, 即
V result = V start if p < 0.5 V end if p ≥ 0.5
按计算值
计算 值中对应的各个分量 使用该值类型所指示的过程进行组合(插值、相加或累积) (参见CSS 值 4 § 3 组合值:插值、相加和累积)。 如果分量数量或对应分量的类型 不匹配, 或者任何分量值使用离散动画 且两个对应值不匹配, 则属性值以离散方式组合。
可重复列表
与按计算 值相同,但如果两个列表的项目数量不同, 则首先将它们重复至项目数量的最小公倍数。 然后每个项目均按计算值组合。 如果一对值无法组合, 或者任何分量值使用离散动画, 则属性值以离散方式组合。

注意: 可重复列表概念确保 一个在概念上重复到某一长度的列表 (例如 background-origin 会重复到 background-image 列表的长度) 或无限重复的列表 能够在任意值之间平滑过渡, 并使计算值 能够正确表示结果 (并且可能被正确继承)。

(参见正文)
某些属性具有上述情况未涵盖的特定插值行为; 在这种情况下,将为该属性明确指定 动画行为。

尚未在其属性定义中包含 动画类型行的属性,其动画类型 在附录 A:现有属性的动画类型中定义。

5.2.1. 自定义属性

对于使用 registerProperty() 方法为当前全局对象注册的自定义属性, 动画类型为按计算值, 由属性的语法定义中使用的类型派生。 当不存在与属性所指定语法相对应的计算值类型 (例如,当语法为通用语法定义时) 或者自定义属性未注册时, 动画类型为离散。

5.3. 关键帧效果

关键帧效果是一种动画效果,它使用计时模型的输出 来更新元素 或伪元素(例如 ::before 或 ::after [select])的 CSS 属性, 该元素或伪元素称为效果目标。

效果目标由一个称为目标元素的 Element 和一个称为目标伪选择器的伪元素 选择器组成。 如果效果目标是一个 Element, 则目标元素就是该元素, 而目标伪选择器为 null。 如果效果目标是一个伪元素, 则目标元素是其起源元素,而目标伪选择器则按指定该特定伪元素所需的形式设置。

请注意,并非所有以这种方式指定的效果目标(例如::part() 伪元素和不受支持的伪元素)都有 已定义的计算属性值。

5.3.1. 关键帧

关键帧效果的效果值 通过在位于小数偏移位置的一系列属性值之间进行插值来计算。 由某个偏移索引的每组属性值称为 一个关键帧。

关键帧的偏移是 [0, 1] 范围内的值或特殊值 null。 关键帧效果的关键帧列表必须 按偏移宽松排序, 这意味着列表中每个具有非 null 关键帧偏移的关键帧, 其偏移都大于或等于列表中前一个具有非 null 关键帧偏移的关键帧的偏移(如果有)。

当关键帧重叠或 具有不受支持的值时的行为 在§ 5.3.4 关键帧效果的效果值中定义。

每个关键帧还关联一个计时函数, 该函数应用于指定它的关键帧与列表中下一个关键帧之间的时间段。 列表中最后一个关键帧上指定的计时函数 永远不会被应用。

每个关键帧都可以具有一个关键帧特定的合成 操作,如果设置,则应用于该关键帧中指定的所有值。 可能的操作及其含义与§ 5.4.4 效果合成中为整个关键帧效果关联的合成操作所定义的完全相同。 如果某个关键帧的关键帧特定的合成操作未设置,则使用为整个关键帧效果指定的合成 操作来处理该关键帧中指定的值。

5.3.2. 计算属性值

给定属性 property、 值 value 和一个 Element element,要计算属性 值: 根据 property 定义表中的“计算值”行解析 value, 使用 element 的计算值作为解析 依赖项的上下文,并返回 结果。

注意: element 上的计算 值不会受到 此算法的影响。

此算法意味着关键帧中指定的属性值可以 建立顺序依赖关系。 当计算属性值时,value 所持有依赖项的计算 值必须先计算。

var animation = elem.animate([{ fontSize: '10px', width: '10em' },
                              { fontSize: '20px', width: '20em' }], 1000);
animation.currentTime = 500;
console.log(getComputedStyle(elem).fontSize); // 应为 15px
console.log(getComputedStyle(elem).width); // 应为 225px

在此示例中,为了针对 10em 计算属性值,我们 必须知道目标 元素上 font-size 的计算值,而该计算值又由 font-size 的效果值决定,而这又要求我们 计算 font-size 的属性值。 因此,计算属性值会受到顺序约束。

5.3.3. 计算已计算关键帧

在计算关键帧 效果的效果 值之前, 会计算其关键帧上的属性值,并计算 任何关键帧 偏移为 null 的关键帧所使用的偏移。解析这些值的结果 是一组已计算关键帧。

一组关键帧的已计算关键帧 偏移,其中包括每个 null 关键帧偏移的适当值, 称为已计算关键帧偏移。

为了产生已计算关键帧偏移,我们定义一个计算 缺失关键帧偏移的过程,该过程接受一系列关键帧 keyframes,并执行以下步骤:

  1. 对于 keyframes 中的每个关键帧, 令该关键帧的已计算关键帧偏移等于其关键帧偏移值。

  2. 如果 keyframes 包含多个关键帧,且 keyframes 中 第一个关键帧的已计算关键帧 偏移为 null, 则将第一个关键帧的已计算关键帧偏移 设置为 0。

  3. 如果 keyframes 中最后一个关键帧的已计算关键帧偏移为 null,则将其已计算关键帧 偏移设置为 1。

  4. 对于每对关键帧 A 和 B,其中:

    按如下方式计算 A 与 B 之间 每个关键帧的已计算关键帧偏移:

    1. 令offsetk 为关键帧 k 的已计算 关键帧偏移。

    2. 令 n 为 A 与 B 之间并 包括 A 和 B 的关键帧数量减 1。

    3. 令 index 表示 keyframe 在 A 与 B 之间的 关键帧序列中的位置,使得 A 之后的第一个关键帧 的 index 为 1。

    4. 将 keyframe 的已计算关键帧偏移设置为offsetA + (offsetB − offsetA) × index / n。

已计算关键帧 使用以下过程产生。 请注意,此过程仅针对具有 可计算属性值的效果目标的关键帧效果执行。

  1. 令 computed keyframes 为一个空的关键帧列表。

  2. 对于此关键帧 效果上指定的关键帧列表中的每个 keyframe,执行以下步骤:

    1. 将一个新的空关键帧 computed keyframe 添加到 computed keyframes。

    2. 对于 keyframe 中指定的每个属性:

      例如,如果 keyframe 对 border-width 属性具有值 "12pt", 则用户代理可以针对每个 普通属性:border-bottom-width、border-left-width、border-right-width 和border-top-width 计算属性值 "16px"。 因此,computed keyframe 不会具有 border-width 属性的值,而是会包含 每个普通属性,并且每个属性的值均为 "16px"。

      如果在展开简写属性或用物理属性替换 逻辑属性时发生冲突,则按顺序应用以下规则, 直到冲突解决:

      1. 普通属性覆盖简写属性(例如 border-top-color 覆盖border-top)。

      2. 普通属性分量较少的简写属性覆盖 普通属性分量较多的简写属性(例如 border-top 覆盖border-color)。

      3. 物理属性覆盖逻辑属性。

      4. 对于具有相同数量普通属性 分量的简写属性,其 IDL 名称(参见CSS 属性到 IDL 属性算法 [CSSOM])在按构成 每个 IDL 名称的 Unicode 码点升序排序时出现得较早的属性, 覆盖出现得较晚的属性。

  3. 将计算缺失关键帧偏移过程应用于 computed keyframes。

  4. 返回 computed keyframes。

5.3.4. 关键帧效果的效果值

对于给定的 iteration progress、current iteration 和 underlying value, 由关键帧 效果作为其目标属性之一引用的单个属性的效果值 按如下方式计算。

  1. 如果 iteration progress 是未解析的,则中止此 过程。

  2. 令 target property 为要计算效果值的普通属性。

  3. 如果 target property 的动画类型 是不可动画,则中止此过程, 因为无法应用该效果。

  4. 如果关键帧 效果没有效果目标, 或者无法计算效果目标的 计算属性值,则中止此过程。

  5. 将合成中性值定义为这样一个值: 当使用 add 合成 操作与底层值组合时,会产生该底层值。

  6. 令 property-specific keyframes 为 获取此关键帧 效果的已计算关键帧集合所得的结果。

  7. 从 property-specific keyframes 中移除所有没有 target property 属性值的关键帧。

  8. 如果 property-specific keyframes 为空,则返回 underlying value。

  9. 如果 property-specific keyframes 中不存在已计算关键帧偏移为 0 的关键帧, 则创建一个新的关键帧,其已计算关键帧偏移为 0,属性值设置为合成中性 值,合成操作为add,并将其前置到 property-specific keyframes 的 开头。

  10. 类似地,如果 property-specific keyframes 中不存在已计算关键帧 偏移为 1 的关键帧, 则创建一个新的关键帧,其已计算关键帧偏移为 1, 属性值设置为合成中性值, 合成操作为add,并将其附加到 property-specific keyframes 的 末尾。

  11. 令 interval endpoints 为一个空的 关键帧序列。

  12. 根据以下第一个匹配的条件执行步骤, 填充 interval endpoints:

    如果 iteration progress < 0,并且 property-specific keyframes 中已计算关键帧偏移为 0 的关键帧不止一个,

    将 property-specific keyframes 中的第一个关键帧添加到 interval endpoints。

    如果 iteration progress ≥ 1,并且 property-specific keyframes 中已计算关键帧偏移为 1 的关键帧不止一个,

    将 property-specific keyframes 中的最后一个关键帧添加到 interval endpoints。

    否则,
    1. 将 property-specific keyframes 中已计算关键帧偏移小于 或等于 iteration progress 且小于 1 的最后一个关键帧附加到 interval endpoints。 如果不存在这样的关键帧(例如, 迭代进度为负),则添加 已计算关键帧偏移为 0 的最后一个关键帧。

    2. 将在上一步添加的关键帧之后, property-specific keyframes 中的下一个关键帧附加到 interval endpoints。

  13. 对于 interval endpoints 中的每个 keyframe:

    1. 如果 keyframe 具有一个不是replace 的合成操作,或者 keyframe 没有 合成操作且此关键帧 效果的合成操作不是replace,则 执行以下步骤:

      1. 令 composite operation to use 为 keyframe 的合成 操作,如果 它没有合成操作,则为此关键帧效果的合成操作。

      2. 令 value to combine 为 keyframe 上指定的 target property 属性值。

      3. 使用与 target property 的动画类型对应的 composite operation to use 过程,将 underlying value(Va)与 value to combine(Vb)组合所得的结果, 替换 keyframe 上 target property 的属性值。

  14. 如果 interval endpoints 中只有一个关键帧,则返回该 关键帧上 target property 的属性值。

  15. 令 start offset 为 interval endpoints 中 第一个关键帧的已计算关键帧偏移。

  16. 令 end offset 为 interval endpoints 中 最后一个关键帧的已计算关键帧偏移。

  17. 令 interval distance 为计算 (iteration progress - start offset) / (end offset - start offset) 所得的结果。

  18. 令 transformed distance 为对 interval endpoints 中第一个关键帧关联的计时函数进行求值所得的结果, 将 interval distance 作为输入进度传入。

  19. 返回应用由 target property 的动画 类型定义的插值过程所得的结果, 对 interval endpoints 中两个关键帧上指定的 target property 值进行插值,将第一个值作为 Vstart,第二个值作为 Vend,并使用 transformed distance 作为插值参数 p。

请注意,此过程假定效果上指定的关键帧列表满足以下条件:

模型的使用者(例如 声明式标记或编程接口)负责确保 满足这些条件。

例如,对于本规范定义的编程 接口,这些条件由产生已计算关键帧的过程 满足,而这些已计算关键帧会成为此过程的输入。

注意: 此过程允许关键帧重叠。 其行为是,在重叠点处,输出值会跳变为 该偏移处最后定义的关键帧的值。 对于偏移 0 或 1 处的重叠关键帧,当迭代 进度值小于 0 或大于等于 1 时,输出值分别为 keyframes 中第一个关键帧或最后一个关键帧的值。

请注意,已计算 关键帧是“实时的”: 用户代理必须表现得如同每次计算效果值时都会重新创建它们一样。

例如,如果 font-size 属性上存在从 10px 到 20px 的进行中过渡,则在关键帧计算期间,关键帧中指定为 1em 的属性值会根据 font-size 上的过渡所产生的 [10px, 20px] 范围内的计算值进行解析。

在存在某些计时函数时,动画效果的输入 迭代 进度并不限于 [0, 1] 范围。 但是,目前关键帧偏移被限制在 [0, 1] 范围内,并且对于此范围之外的输入 迭代进度值,只是简单地对属性值进行外推。

我们曾考虑移除此限制,因为在某些情况下, 能够在 [0, 1] 范围之外的迭代进度值处指定属性值的 非线性变化是有用的。 一个示例是从绿色插值到黄色的动画, 但它具有一个过冲计时函数,使其在回落到 黄色之前暂时插值“超过”黄色而达到红色。

虽然可以通过修改关键帧 和计时函数来实现此效果,但这种方法似乎破坏了模型中 计时关注点与动画效果之间的分离。

尚不清楚应如何实现此效果,但我们注意到, 允许关键帧偏移超出 [0, 1] 可能会使当前 指定的、根据需要合成偏移 0 和 1 处关键帧的行为 变得不一致。

参见东京 2013 F2F 会议记录讨论的第 4 节([0, 1] 范围之外的关键帧偏移)。

<https://github.com/w3c/csswg-drafts/issues/2081>

5.4. 组合效果

本节为非规范性内容

在计算关键帧效果的效果 值后,它们会应用于动画 效果的目标 属性。

由于可能有多个生效中的关键帧 效果以同一个属性为目标,因此通常需要将多个关键帧效果的结果组合在一起。 此过程称为合成,它基于为每个 被生效中的动画效果作为目标的属性建立一个效果栈。

将关键帧 效果的结果合成在一起后, 合成结果会与为目标属性指定的其他 值组合。

其结构如下图所示:

将效果值应用于其目标属性的概述
将效果值应用于 其目标 属性的概述。
以同一个属性为目标的关键帧效果的结果 使用效果栈合成在一起。
然后,此合成结果会插入 CSS 层叠中的适当位置。

对于此操作的第一部分——组合以同一个属性为目标的效果值——需要 同时确定如何将关键帧效果彼此组合, 以及它们应用的顺序,即 它们的相对合成顺序。

如何组合效果 值由 对应关键帧效果的合成操作决定。

效果值的相对合成顺序由为每个 动画属性建立的效果栈决定。

5.4.1. 动画类别

本规范提供一个通用动画模型,供 其他在此模型之上定义标记或编程接口的规范使用。 生成动画的特定标记或编程接口定义其动画类别。

后续规范可以定义不同动画类别之间 或特定类别内部合成顺序的专用行为。

本节为非规范性内容

例如,类别为“CSS animation”的动画被定义为具有比类别为“CSS transition”的动画更高的合成顺序, 但比 没有特定类别的其他动画更低。

在“CSS animation”对象集合中,会基于 animation-name 属性 以及其他因素定义专用的 合成顺序。

5.4.2. 效果栈

一个效果栈与由一个或多个关键帧效果作为目标的每个属性相关联。 效果栈建立关键帧效果的相对合成顺序。

在一个效果栈中,任意两个关键帧效果 A 和 B 的相对合成顺序 通过如下比较它们的属性来 确定:

  1. 令动画效果的关联动画为 与该动画效果关联的动画。

  2. 依次应用以下条件对 A 和 B 排序,直到 顺序确定,

    1. 如果 A 和 B 的关联动画在类别上不同, 则按为对应类别定义的任何类别间合成顺序 排序。

    2. 如果 A 和 B 仍未排序, 则按 A 和 B 的关联动画共有类别所定义的任何类别特定合成顺序 排序。

    3. 如果 A 和 B 仍未排序, 则按它们的关联动画在全局动画列表中的位置排序。

排序更靠前的动画效果 具有更低的合成顺序。

5.4.3. 计算效果栈的结果

为了计算效果栈的最终值, 栈中每个关键帧效果的效果值 会按合成顺序组合。

对效果栈进行求值的过程中的每一步都以 一个底层值 作为输入。

对于栈中的每个关键帧效果, 来自该关键帧 效果的相应效果值会与底层值组合以产生一个新值。 所得值会成为用于组合栈中下一个关键帧效果的底层值。

效果 栈的最终值称为合成值,它就是将栈中最后一个 (合成顺序最高的)关键帧效果的效果值与此时的底层值组合所得的结果。

5.4.4. 效果合成

用于将效果值与底层值组合的具体操作由产生该效果值的关键帧效果的合成操作决定。

本规范定义了三种合成操作, 如下:

替换

将效果值与底层值合成的结果就是效果值。

相加

将效果值相加到底层 值上。 对于相加操作被定义为 不满足交换律的动画类型, 操作数的顺序为 底层值 + 效果值。

累积

将效果值累积到底层值上。 对于累积操作被 定义为不满足交换律的动画类型, 操作数的顺序 为底层值,然后是效果值。

5.4.5. 应用合成结果

通过向 CSS 层叠添加一个指定值,可以将合成 值应用于目标 属性。

此指定值所添加到的层叠级别 取决于给定属性的效果栈中合成顺序最高的效果所关联的动画的类别。 默认情况下,指定值会添加到层叠的“动画 声明”级别([css-cascade-3])。

本节为非规范性内容

例如,如果合成顺序最高的效果与 “CSS 过渡”类别的动画关联,则合成值将添加到层叠的“过渡声明”级别。

为 CSS 目标 属性计算的合成值 使用以下过程应用。

  1. 计算属性的 base value, 即在不存在动画的情况下计算该属性的计算值 所生成的该属性值。

  2. 为该属性建立效果 栈(参见§ 5.4.2 效果栈)。

  3. 计算效果 栈的合成值,传入属性的 base value 作为初始底层值(参见§ 5.4.3 计算效果栈的 结果)。

  4. 将合成 值插入 CSS 层叠中 为目标属性所建立的效果栈顶部效果所关联的动画的类别所定义的级别。

5.5. 替换动画

本节为非规范性内容

使用本规范中定义的编程接口,可以 反复触发无限期地影响元素动画样式的新动画。

例如,考虑以下代码:

elem.addEventListener('mousemove', evt => {
  circle.animate(
    { transform: `translate(${evt.clientX}px, ${evt.clientY}px)` },
    { duration: 500, fill: 'forwards' }
  );
});

每次移动鼠标时,这都会生成一个新的 forwards 填充动画, 很快就会产生数百甚至数千个 forwards 填充 动画。

如果要求用户代理保留所有此类动画, 动画列表将无限增长, 从而产生内存泄漏。

本节定义了一种机制,使被覆盖的动画 自动移除,除非作者明确要求保留它们。

5.5.1. 替换状态

一个动画维护 一个替换状态,其可以 是以下 值之一:

动画的替换状态的初始值为活动。

如果一个动画的替换状态为已移除,则其动画效果 不会包含在其目标 属性的效果 栈中。

5.5.2. 移除已替换的动画

如果以下所有条件 均为真,则一个动画是可替换的:

当被要求针对 Document doc 移除已替换的动画时, 对于满足以下条件的每个动画 animation:

执行以下步骤:

  1. 将 animation 的替换状态设置为已移除。

  2. 创建一个 AnimationPlaybackEvent, removeEvent。

  3. 将 removeEvent 的 type 属性设置为remove。

  4. 将 removeEvent 的 currentTime 属性 设置为 animation 的当前时间。

  5. 将 removeEvent 的 timelineTime 属性设置为与 animation 关联的时间线的当前时间。

  6. 如果 animation 有用于计时的文档,则将 removeEvent 与其目标 animation 一同附加到其用于 计时的文档的待处理动画事件队列。 对于预定事件时间,使用 将时间线时间转换为相对于原点的时间的过程应用于 与 animation 关联的时间线的当前时间所得的结果。

    否则,将一个任务加入队列,以在 animation 上分派 removeEvent。 此任务的任务源是DOM 操作任务 源。

5.6. 动画的副作用

对于每个至少被一个处于当前或生效中的动画效果作为目标的属性, 并且该动画效果与一个替换状态不是已移除的动画关联, 用户代理必须表现得如同效果目标上的will-change 属性([css-will-change-1])包含该属性一样。

本节为非规范性内容

由于上述要求,例如,如果一个动画以元素的 transform 属性为目标,那么只要该动画处于 前阶段、活动阶段 或者,如果它具有 "forwards" 或 "both" 的填充模式, 则处于后阶段,就会为效果目标创建层叠上下文。

6. 编程接口

本节为非规范性内容

除了上述抽象模型外,Web 动画还 定义了一个用于该模型的编程接口。 此接口可用于检查和扩展通过 声明式方式生成的动画,或者在 过程式方法更合适时直接生成动画。

6.1. 编程接口中的时间值

时间值在 编程接口中使用 double 类型表示。未解析的时间值 使用值 null 表示。

6.2. AnimationTimeline 接口

时间线在 Web 动画 API 中由 AnimationTimeline 接口表示。

[Exposed=Window]
interface AnimationTimeline {
    readonly attribute double? currentTime;
};
currentTime, 类型为 double,只读,可为空

返回此时间线的当前时间, 如果此时间线是非活动的,则返回 null。

6.3. DocumentTimeline 接口

文档时间线, 包括默认文档 时间线,在 Web 动画 API 中由 DocumentTimeline 接口表示。

dictionary DocumentTimelineOptions {
  DOMHighResTimeStamp originTime = 0;
};

[Exposed=Window]
interface DocumentTimeline : AnimationTimeline {
  constructor(optional DocumentTimelineOptions options = {});
};
originTime, 类型为 DOMHighResTimeStamp,默认为 0

时间线的原点时间, 指定为相对于时间原点的实数 毫秒数。

DocumentTimeline (options)

创建一个新的 DocumentTimeline。 与该时间线关联的 Document 是与作为当前全局对象的 Window 关联的 Document。

options

新创建时间线的配置参数。 本规范仅定义 originTime 成员,但其他规范 可以扩展此集合。

6.4. Animation 接口

动画在 Web 动画 API 中由 Animation 接口表示。

[Exposed=Window]
interface Animation : EventTarget {
    constructor(optional AnimationEffect? effect = null,
                optional AnimationTimeline? timeline);
             attribute DOMString                id;
             attribute AnimationEffect?         effect;
             attribute AnimationTimeline?       timeline;
             attribute double?                  startTime;
             attribute double?                  currentTime;
             attribute double                   playbackRate;
    readonly attribute AnimationPlayState       playState;
    readonly attribute AnimationReplaceState    replaceState;
    readonly attribute boolean                  pending;
    readonly attribute Promise<Animation>       ready;
    readonly attribute Promise<Animation>       finished;
             attribute EventHandler             onfinish;
             attribute EventHandler             oncancel;
             attribute EventHandler             onremove;
    undefined cancel();
    undefined finish();
    undefined play();
    undefined pause();
    undefined updatePlaybackRate(double playbackRate);
    undefined reverse();
    undefined persist();
    [CEReactions]
    undefined commitStyles();
};
Animation (effect, timeline)

使用以下过程创建一个新的 Animation 对象。

  1. 令 animation 为一个新的 Animation 对象。

  2. 针对 animation 运行设置动画的时间线过程,将 timeline 作为 new timeline 传入,或者,如果缺少 timeline 参数, 则传入与作为当前全局对象的 Window 关联的 Document 的默认文档时间线。

  3. 针对 animation 运行设置动画的关联 效果过程,将 source 作为 new effect 传入。

effect

一个可选值,如果不为 null,则指定要分配给新创建动画的关联 效果。

timeline

一个可选值,如果存在,则指定要与 新创建动画关联的时间线。 如果缺少,则使用与作为当前全局对象的 Window 关联的 Document 的默认文档时间线。

id, 类型为 DOMString

用于标识动画的字符串。

effect, 类型为 AnimationEffect,可为空

此动画的关联效果。 设置此属性会使用设置动画的关联效果过程 更新对象的关联效果。

timeline, 类型为 AnimationTimeline,可为空

与此动画关联的时间线。 设置此属性会使用 设置动画的时间线过程更新对象的时间线。

startTime, 类型为 double,可为空

返回此动画的开始时间。 设置此属性会使用 设置开始时间过程将此对象的开始时间 更新为新值。

currentTime, 类型为 double,可为空

此动画的当前时间。 设置此属性遵循设置 当前时间过程,将此对象的当前时间设置为新值。

playbackRate, 类型为 double

此动画的播放速率。 设置此属性遵循设置播放速率过程, 将此对象的播放速率设置为新值。

设置此属性会同步更新播放 速率,这意味着它不会尝试与 在单独进程或线程中运行动画的播放状态同步。 因此,为进行中的 动画设置 playbackRate 可能会导致动画发生跳变。

要为进行中的动画设置播放速率并使其 平滑更新,请使用异步的 updatePlaybackRate() 方法。

playState, 类型为 AnimationPlayState,只读

此动画的播放 状态。

replaceState, 类型为 AnimationReplaceState,只读

此动画的替换状态。

pending, 类型为 boolean,只读

如果此动画有待处理播放任务或 待处理 暂停任务,则返回 true。

ready, 类型为 Promise<Animation>, 只读

返回此对象的当前就绪 promise。

finished, 类型为 Promise<Animation>,只读

返回此对象的当前已完成 promise。

onfinish, 类型为 EventHandler

finish 事件的事件处理程序。

oncancel, 类型为 EventHandler

cancel 事件的事件处理程序。

onremove, 类型为 EventHandler

remove 事件的事件处理程序。

void cancel()

通过针对此对象运行取消动画过程, 清除由此动画造成的所有效果并中止其播放。

void finish()

通过针对此对象运行完成动画过程, 将动画沿当前方向定位到关联效果结束时间。

类型为 InvalidStateError 的 DOMException

如果此动画的播放速率为零,或者此 动画的播放速率 > 零且 关联效果结束时间为无穷大,则抛出。

void play()

通过运行播放动画过程并将 true 作为 auto-rewind 标志的值传入,开始或恢复动画播放。

void pause()

通过针对 此对象运行暂停动画过程来暂停此动画的播放。

void updatePlaybackRate(playbackRate)

通过执行无缝更新播放速率 过程,并将 playbackRate 作为 new playback rate 传入,对此动画的播放速率执行异步更新。

playbackRate

指定要使用的更新后播放速率的有限实数。

void reverse()

反转此动画的播放 速率,并使用针对此对象的反向播放动画 过程播放它。 与 play() 一样,此 方法会取消动画的暂停状态,并且如果动画已经沿反向方向 播放完毕,则定位到关联 效果的开头。

void persist()

将此动画的替换状态设置为已持久化。

void commitStyles()

使用提交计算样式过程,将此动画的动画 效果产生的当前效果值写入其对应效果 目标的内联样式。

与此接口上定义的大多数其他方法不同,调用此方法确实会触发 样式更改事件(参见§ 6.13 模型活性)。

由于提交计算样式过程即使动画处于已移除状态,也会包含该动画的效果值, 因此此方法可用于在动画 被替换后保留其效果(参见§ 5.5.2 移除 已替换的动画), 而无需保留实际动画。

请注意,提交的值是调用此方法时 动画效果产生的计算值。 由于这些值是计算值,因此它们不会像实时动画产生的值那样 反映上下文的变化,例如响应 CSS 变量的变化,或者 根据计算后的 font-size 的变化 重新计算 em 单位。

为了在填充动画被替换后完整保留其结果 (参见§ 5.5 替换动画), 可以使用 persist() 方法, 但请注意,这样做意味着动画会继续消耗 资源。

要针对动画 animation 提交计算 样式:

  1. 令 targets 为与 animation 关联的动画效果的所有效果目标的集合。

  2. 对于 targets 中的每个 target:

    1. 如果 target 不是能够具有style 属性 [CSS-STYLE-ATTR] 的元素(例如,它是伪元素 或者是某种未定义 style 属性的 文档格式中的元素),则抛出一个 “NoModificationAllowedError” DOMException 并中止这些步骤。

    2. 如果在应用任何待处理的样式更改后,target 未正在渲染,则抛出一个 “InvalidStateError” DOMException 并中止这些步骤。

      关于 display: contents,正在渲染 [HTML] 的定义仍在讨论中。 对于此过程,我们假设一个具有 display: contents 且原本会具有 相关布局框的元素(即它是已连接的, 并且不属于 display: none 子树) 正在渲染。

    3. 令 inline style 为获取与 target 的style 属性对应的CSS 声明块所得的结果。 如果 target 不具有style 属性, 则令 inline style 为一个新的空CSS 声明块,并将 所有者节点设置为 target。

    4. 令 targeted properties 为物理普通属性的集合, 这些属性是至少一个与 animation 关联且其效果目标为 target 的动画 效果的目标属性。

    5. 对于 targeted properties 中的每个属性 property:

      1. 令 partialEffectStack 为 target 上 property 的效果栈的副本。

      2. 如果 animation 的替换状态为已移除, 则将所有与 animation 关联、其效果目标为 target,并且将 property 包含为目标属性的动画效果添加到 partialEffectStack。

      3. 从 partialEffectStack 中移除所有 其关联动画的合成顺序高于 animation 的动画效果。

      4. 令 effect value 为使用 target 的计算 样式计算 property 的 partialEffectStack 结果所得的结果(参见§ 5.4.3 计算 效果栈的结果)。

      5. 在 inline style 中为 effect value 设置 CSS 声明 property。

    6. 为 inline style 更新 style 属性。

6.4.1. AnimationPlayState 枚举

enum AnimationPlayState { "idle", "running", "paused", "finished" };
idle

对应于空闲 播放状态。

running

对应于正在运行的播放状态。

paused

对应于已暂停播放状态。

finished

对应于已完成播放状态。

6.4.2. AnimationReplaceState 枚举

enum AnimationReplaceState { "active", "removed", "persisted" };
active

对应于活动替换状态。

removed

对应于已移除替换状态。

persisted

对应于已持久化替换状态。

6.5. AnimationEffect 接口

动画效果在 Web 动画 API 中由抽象的 AnimationEffect 接口表示。

[Exposed=Window]
interface AnimationEffect {
    EffectTiming         getTiming();
    ComputedEffectTiming getComputedTiming();
    undefined            updateTiming(optional OptionalEffectTiming timing = {});
};
将来,我们可能会公开 any onupdate (double? progress, double currentIteration, Animatable? target, any underlyingValue),以便可以独立于计时模型驱动动画效果。
getTiming()

返回此动画效果的指定计时属性。

有关返回的 EffectTiming 对象的成员与计时模型属性之间的对应关系,请参见 EffectTiming 接口。

getComputedTiming()

返回此动画 效果的已计算计时属性。

尽管 getTiming() 和 getComputedTiming() 返回的对象中有一些属性是相同的,但它们的值可能存在以下差异:

注意: 将来其他计时成员很可能也会被 扩展为包含类似 auto 的值。在执行计时计算时, 建议作者尽可能使用 getComputedTiming(), 以避免在允许的指定值范围或类型 发生变化时产生不兼容问题。

除了返回值可能存在差异之外, 与 getTiming() 相比,getComputedTiming() 还会返回由 ComputedEffectTiming 字典定义的额外计时 信息。

updateTiming(timing)

通过执行更新动画 效果的计时属性过程,并将 timing 参数作为 input 传入,更新此动画效果的 指定计时属性。

optional OptionalEffectTiming timing

要更新的计时属性。 与 timing 上任何不存在的成员对应的计时属性 将不会被修改。

remove() 方法可用于从其父组或动画中移除效果。 我们是否应该将其保留在第 1 层级中,并简单定义为从动画中移除动画效果?[问题 #2082]

6.5.1. EffectTiming 和 OptionalEffectTiming 字典

EffectTiming 字典表示 AnimationEffect 的计时属性。

OptionalEffectTiming 字典是 EffectTiming 字典的一种变体,允许某些成员不存在。 AnimationEffect 接口的 updateTiming() 方法使用它来对动画效果的计时属性 执行增量更新。

dictionary EffectTiming {
    double                             delay = 0;
    double                             endDelay = 0;
    FillMode                           fill = "auto";
    double                             iterationStart = 0.0;
    unrestricted double                iterations = 1.0;
    (unrestricted double or DOMString) duration = "auto";
    PlaybackDirection                  direction = "normal";
    DOMString                          easing = "linear";
};

dictionary OptionalEffectTiming {
    double                             delay;
    double                             endDelay;
    FillMode                           fill;
    double                             iterationStart;
    unrestricted double                iterations;
    (unrestricted double or DOMString) duration;
    PlaybackDirection                  direction;
    DOMString                          easing;
};
delay, 类型为 double,默认为 0

开始延迟表示 从关联动画的开始时间到活动 区间开始之间的毫秒数。

endDelay, 类型为 double,默认为 0

结束延迟表示 从动画效果的活动区间结束到其结束时间之间的毫秒数。

fill, 类型为 FillMode,默认为 "auto"

填充模式定义 动画 效果在其活动区间之外的行为。

执行计时计算时,特殊字符串值 auto 会按如下方式展开为 计时模型所识别的填充模式之一,

如果应用该填充模式的动画效果 是关键帧效果,

使用 none 作为填充 模式。

否则,

使用 both 作为填充 模式。

如§ 4.6 填充行为中所述,不建议作者使用 无限期填充的动画。
iterationStart, 类型为 double,默认为 0.0

动画 效果的迭代起点属性是一个 大于或等于零的有限实数,表示动画效果开始时的迭代 索引以及其在该迭代中的进度。

例如,值 0.5 表示动画效果从第一次迭代进行到一半的位置开始。值 1.2 表示动画 效果从第二次迭代进行到 20% 的位置开始。

请注意,iterations 的值实际上会加到 iterationStart 上,因此,一个 iterationStart 为 "0.5" 且 iterations 为 "2" 的动画效果仍会重复两次,不过它会在其迭代区间的中点开始 并在中点结束。

iterationStart 大于或 等于一的值通常只有在与具有迭代合成 操作为 accumulate 的动画效果结合使用,或者当前 迭代索引在其他情况下具有意义时才有用。

iterations, 类型为 unrestricted double,默认为 1.0

动画 效果的迭代次数属性是 一个大于或等于零的实数(包括正 无穷大),表示动画效果重复的次数。

可以将其设置为 +Infinity,使 动画 效果无限重复(除非效果的持续时间为 零,在这种情况下它会立即完成)。

duration, 类型为 (unrestricted double or DOMString),默认为 "auto"

迭代持续时间是一个大于或 等于零的实数(包括正无穷大),表示 完成动画 效果单次迭代所需的时间。

在本规范的这一层级中,为进行计时模型计算 以及作为 getComputedTiming() 返回的 duration 成员的结果,字符串值 auto 被视为零。 但是,如果作者指定了 auto 值,用户代理必须为 getTiming() 返回的 duration 成员返回 auto。

这是一项向前兼容措施,因为预计本规范的未来层级将引入组效果,其中 auto 值会展开为包含子 效果的持续时间。

direction, 类型为 PlaybackDirection,默认为 "normal"

动画效果的播放 方向,它定义播放是向前进行、向后进行,还是 在每次迭代时交替进行。

easing, 类型为 DOMString,默认为 "linear"

用于缩放时间以 产生缓动效果的计时函数。

字符串的语法由 <easing-function> 产生式 [CSS-EASING-1] 定义。

6.5.2. FillMode 枚举

enum FillMode { "none", "forwards", "backwards", "both", "auto" };
none

不填充。

forwards

向前填充。

backwards

向后填充。

both

向后和向前填充。

auto

不填充。 在本规范的后续层级中,对于其他类型的动画效果,这可能产生不同的 行为。

6.5.3. PlaybackDirection 枚举

enum PlaybackDirection { "normal", "reverse", "alternate", "alternate-reverse" };
normal

所有迭代均按指定方式播放。

reverse

所有迭代均以与指定顺序 相反的方向播放。

alternate

偶数次迭代按指定方式播放,奇数次迭代以 与指定顺序相反的方向播放。

alternate-reverse

偶数次迭代以与指定顺序相反的方向 播放,奇数次迭代按指定方式播放。

6.5.4. 更新 AnimationEffect 的计时

要根据 EffectTiming 或 OptionalEffectTiming 对象 input 更新动画效果的计时属性 effect,执行 以下步骤:

  1. 如果 input 的 iterationStart 成员存在且小于零,则抛出一个 TypeError 并中止此过程。

    注意: 使用 TypeError 而不是 RangeError 的原因是为了与 WebIDL 的 [EnforceRange] 注解的行为保持一致,以防该 注解将来能够用于浮点值。

  2. 如果 input 的 iterations 成员存在, 并且小于零或值为 NaN,则抛出一个 TypeError 并中止此过程。

  3. 如果 input 的 duration 成员存在, 并且小于零或值为 NaN,则抛出一个 TypeError 并中止此过程。

  4. 如果 input 的 easing 成员存在,但无法使用 <easing-function> 产生式 [CSS-EASING-1] 解析,则抛出一个 TypeError 并中止此 过程。

  5. 按如下方式,将 input 中每个存在的成员赋值给 effect 对应的计时属性:

6.5.5. ComputedEffectTiming 字典

计时模型计算出的计时属性使用 ComputedEffectTiming 字典对象公开。

dictionary ComputedEffectTiming : EffectTiming {
    unrestricted double  endTime;
    unrestricted double  activeDuration;
    double?              localTime;
    double?              progress;
    unrestricted double? currentIteration;
};
endTime, 类型为 unrestricted double

动画 效果的结束时间, 表示为从零局部时间起的毫秒数(即,如果此动画 效果与一个动画关联,则从 关联动画的开始时间起)。 这对应于动画 效果的活动 区间结束加上任何结束 延迟。

activeDuration, 类型为 unrestricted double

此动画效果的活动 持续时间。

localTime, 类型为 double,可为空

此 动画 效果的局部时间。

如果此动画效果未与一个 动画关联,则该值为 null。

progress, 类型为 double,可为空

此动画效果的当前迭代进度。

currentIteration, 类型为 unrestricted double,可为空

当前 迭代索引,第一次 迭代从零开始。

在大多数情况下,这将是一个(正)整数。但是,对于 重复无限次的零持续时间动画,该值 将为正 Infinity。

与未解析 时间一样,未解析的当前迭代 使用 null 值表示。

6.6. KeyframeEffect 接口

关键帧效果由 KeyframeEffect 接口表示。

[Exposed=Window]
interface KeyframeEffect : AnimationEffect {
    constructor(Element? target,
                object? keyframes,
                optional (unrestricted double or KeyframeEffectOptions) options = {});
    constructor(KeyframeEffect source);
    attribute Element?           target;
    attribute CSSOMString?       pseudoElement;
    attribute CompositeOperation composite;
    sequence<object> getKeyframes();
    undefined        setKeyframes(object? keyframes);
};
KeyframeEffect (target, keyframes, options)

使用以下过程创建一个新的 KeyframeEffect 对象:

  1. 创建一个新的 KeyframeEffect 对象 effect。

  2. 将 effect 的目标元素 设置为 target。

  3. 将目标伪选择器设置为以下 第一个匹配条件所对应的结果。

    如果 options 是一个具有 pseudoElement 属性的 KeyframeEffectOptions 对象,

    将目标 伪选择器设置为 pseudoElement 属性的值。

    为此属性赋值时,会应用接口上的 pseudoElement setter 所定义的错误处理。 如果 setter 要求抛出异常,则此过程 必须抛出相同的异常并中止所有后续步骤。

    否则,

    将目标 伪选择器设置为 null。

  4. 令 timing input 为以下第一个 匹配条件所对应的结果。

    如果 options 是一个 KeyframeEffectOptions 对象,

    令 timing input 为 options。

    否则(如果 options 是一个 double),

    令 timing input 为一个新的 EffectTiming 对象,将所有成员设置为其默认值, 并将 duration 设置为 options。

  5. 调用更新动画 效果的计时属性过程,以根据 timing input 更新 effect。

    如果该过程导致抛出异常,则传播该 异常并中止此过程。

  6. 如果 options 是一个 KeyframeEffectOptions 对象,则将 effect 的 composite 属性赋值为 options 中的对应值。

    为此属性赋值时,会应用 KeyframeEffect 接口上对应 setter 所定义的错误处理。 如果 setter 要求对 options 指定的值抛出异常, 则此过程必须抛出 相同的异常并中止所有后续步骤。

  7. 通过执行为 setKeyframes() 定义的过程,并将 keyframes 作为输入传入,初始化关键帧集合。

Element? target

目标元素。 对于不以 特定元素为目标的动画,它可以是 null。

object? keyframes

要使用的关键帧集合。 此参数的格式和处理在§ 6.6.3 处理 keyframes 参数中定义。

optional KeyframeEffectOptions options

可以是一个指定效果迭代持续时间的数字, 也可以是一组指定效果计时和行为的 属性。

此构造函数的用法示例参见§ 6.6.1 创建新的 KeyframeEffect 对象。

KeyframeEffect (source)

使用以下过程创建一个与 source 具有相同属性的新的 KeyframeEffect 对象:

  1. 创建一个新的 KeyframeEffect 对象 effect。

  2. 使用 source 的 对应值设置 effect 的以下属性:

    注意: 与 KeyframeEffect(target, keyframes, options) 构造函数不同, 我们无需重新抛出异常,因为可以假定 source 上指定的计时属性是有效的。

KeyframeEffect source

从中复制用于定义新关键帧效果的 属性的关键帧效果。

target, 类型为 Element,可为空

由此对象进行动画处理的目标元素 (如果效果目标是一个 Element 则为效果目标本身,如果它是伪元素则为其起源元素)。 对于不以 特定元素为目标的动画,例如使用音频 API 产生声音的动画,此值可以为 null。

pseudoElement, 类型为 CSSOMString,可为空

目标伪选择器。 如果此效果没有效果 目标,或者 效果目标是元素 (即不是伪元素),则为 null。 当效果目标是 伪元素时, 此值指定伪元素选择器(例如 ::before)。

设置时, 在应用以下异常处理后,将目标伪选择器设置为此动画 效果所提供的值:

  • 如果提供的值不是 null,并且是无效的 <pseudo-element-selector>, 则用户代理必须抛出 一个错误名称为 SyntaxError 的 DOMException, 并保持此动画效果的目标伪选择器不变。

    注意,此上下文中的“无效”遵循 [SELECTORS-4] 中定义的无效选择器定义,因此 语法无效的伪元素以及用户代理 没有可用支持级别的伪元素 都被视为无效。

  • 如果指定了传统的 Selectors Level 2 单冒号选择器之一 (':before'、':after'、':first-letter' 或 ':first-line'), 则必须将目标伪选择器 设置为 等效的双冒号选择器(例如 '::before')。

composite, 类型为 CompositeOperation

用于将此关键帧 效果与效果栈合成的合成操作,由 CompositeOperation 枚举 值之一指定。

设置时,将此动画 效果的合成操作属性设置为提供的值。

sequence<object> getKeyframes()

返回构成此效果的关键帧及其已计算关键帧 偏移。

本节为非规范性内容

此方法的结果是具有以下 格式的对象序列:

dictionary ComputedKeyframe {
    // ... 属性-值对 ...
    // 即 DOMString propertyName
    double?                  offset = null;
    double                   computedOffset;
    DOMString                easing = "linear";
    CompositeOperationOrAuto composite = "auto";
};

每个成员的含义和值如下:

offset

关键帧的关键帧偏移, 指定为 0.0 到 1.0(含)之间的数字,或 null。

如果关键帧要在 相邻关键帧之间自动均匀分布,则该值为 null。

computedOffset

此关键帧的已计算关键帧偏移, 作为运行计算缺失关键帧 偏移过程的一部分计算。

与 offset 成员不同,computedOffset 永远不会为 null。

easing

用于变换 从此关键帧到序列中下一个关键帧之间时间进度的计时函数。

composite

用于将 此关键帧中指定的值与底层值组合的关键帧特定合成 操作。

如果正在使用关键帧效果上指定的合成操作, 则此成员将为 auto。

由于关键帧由一种部分开放的字典 类型表示,而这种类型目前无法用 WebIDL 表达,因此用于 准备此方法结果的过程在下面以文字形式定义:

  1. 令 result 为一个空的对象序列。

  2. 令 keyframes 为以下之一:

    1. 如果此关键帧效果与 CSSAnimation 关联, 并且其关键帧尚未被 对 setKeyframes() 的成功调用所替换; 则为此关键帧效果的已计算关键帧。

    2. 否则,为将计算缺失关键帧 偏移过程应用于此关键帧(属于此关键帧效果)所得的结果。

    注意: 对于 CSS 动画,我们返回已计算关键帧, 因为并非 CSS 中指定的所有关键帧都可以 由字典表示。

  3. 对于 keyframes 中的每个 keyframe,执行以下步骤:

    1. 使用 以下定义初始化字典对象 output keyframe:

      dictionary BaseComputedKeyframe {
           double?                  offset = null;
           double                   computedOffset;
           DOMString                easing = "linear";
           CompositeOperationOrAuto composite = "auto";
      };
      
    2. 将 output keyframe 的 offset、 computedOffset、 easing、 和 composite 成员分别设置为 keyframe 的关键帧偏移、 已计算关键帧偏移、 关键帧特定的计时函数和关键帧特定 合成操作值。

    3. 对于 keyframe 中的每个动画属性-值对, declaration,执行以下步骤:

      1. 令 property name 为将动画 属性名称到 IDL 属性名称算法 应用于 declaration 的属性名称所得的结果。

      2. 令 IDL value 为通过将 declaration 传递给序列化 CSS 值算法 [CSSOM],对 declaration 的属性值进行序列化所得的结果。

      3. 令 value 为将 IDL value 转换为 ECMAScript String 值所得的结果。

      4. 在 output keyframe 上调用 [[DefineOwnProperty]] 内部方法,属性名称为 property name, Property Descriptor { [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true, [[Value]]: value } 且布尔标志为 false.

    4. 将 output keyframe 附加到 result。

  4. 返回 result。

void setKeyframes(object? keyframes)

替换构成此效果的关键帧集合。

object? keyframes

一系列关键帧,其格式和处理由§ 6.6.3 处理 keyframes 参数定义。

此效果的关键帧集合会替换为 执行处理 keyframes 参数过程所得的结果。 如果该过程抛出异常,则不会修改此效果的关键帧。

6.6.1. 创建新的 KeyframeEffect 对象

本节为非规范性内容

KeyframeEffect 构造函数 提供了多种创建新的 KeyframeEffect 对象的方法。

最简单的情况下,可以按如下方式构造一个 KeyframeEffect 对象,使 elem 的 "left" 属性在三 秒内变为 100px:

var effect = new KeyframeEffect(elem, { left: '100px' }, 3000);

表示关键帧列表的第二个参数可以 指定多个属性。(参见§ 6.6.3 处理 keyframes 参数。)

// 一次指定多个属性
var effectA = new KeyframeEffect(elem, { left: '100px', top: '300px' }, 3000);

// 指定多个关键帧
var effectB = new KeyframeEffect(elem, [ { left: '100px' }, { left: '300px' } ], 3000);

表示动画计时的第三个参数可以 像上面那样只是一个表示迭代持续时间(以 毫秒为单位)的数字,或者,为了指定更多计时属性,例如 开始延迟,可以使用 EffectTiming 对象,如下所示:

var effect =
  new KeyframeEffect(elem, { left: '100px' }, { duration: 3000, delay: 2000 });

如果未指定持续时间,则使用零值。 可以按如下方式创建一个只设置属性 而不进行任何插值的动画:

var effect =
  new KeyframeEffect(elem, { visibility: 'hidden' }, { fill: 'forwards' });

不过,如§ 4.6 填充行为中所述,不建议以这种方式使用无限期填充的 动画。

创建 KeyframeEffect 后, 可以通过将其添加到 Animation 中,然后播放该动画来播放它。 不过,对于简单效果,Element.animate 快捷方式更加 方便,因为它会自动执行这些步骤。例如,

elem.animate({ left: '100px' }, 3000);

6.6.2. 属性名称和 IDL 名称

针对 property 的动画属性名称到 IDL 属性名称 算法如下:

  1. 如果 property 符合 <custom-property-name> 产生式, 则返回 property。

  2. 如果 property 指代 CSS float 属性, 则返回字符串 "cssFloat"。

  3. 如果 property 指代 CSS offset 属性, 则返回字符串 "cssOffset"。

  4. 否则,返回将CSS 属性到 IDL 属性算法 [CSSOM] 应用于 property 所得的结果。

针对 attribute 的IDL 属性名称到动画属性名称 算法如下:

  1. 如果 attribute 符合 <custom-property-name> 产生式, 则返回 attribute。

  2. 如果 attribute 是字符串 "cssFloat",则返回 一个表示 CSS float 属性的动画属性。

  3. 如果 attribute 是字符串 "cssOffset",则返回 一个表示 CSS offset 属性的动画属性。

  4. 否则,返回将IDL 属性到 CSS 属性算法 [CSSOM] 应用于 attribute 所得的结果。

6.6.3. 处理 keyframes 参数

本节为非规范性内容

以下方法都接受一组关键帧作为参数:

此参数可以采用以下所示的两种形式之一指定。

// 以下两个表达式产生相同的结果:
elem.animate([ { color: 'blue' },
               { color: 'green' },
               { color: 'red' },
               { color: 'yellow' } ], 2000);
elem.animate({ color: [ 'blue', 'green', 'red', 'yellow' ] }, 2000);

// 同样,对于多属性动画,以下两个
// 表达式是等价的:
elem.animate([ { color: 'blue', left: '0px' },
               { color: 'green', left: '-20px' },
               { color: 'red', left: '100px' },
               { color: 'yellow', left: '50px'} ], 2000);
elem.animate({ color: [ 'blue', 'green', 'red', 'yellow' ],
               left: [ '0px', '-20px', '100px', '50px' ] }, 2000);

// 顺带一提,以下三个表达式都是等价的:
elem.animate([ { color: 'red' } ], 1000);
elem.animate({ color: [ 'red' ] }, 1000);
elem.animate({ color: 'red' }, 1000);

第一种形式(数组形式)由关键帧数组组成,其中每个 关键帧对于每个动画属性最多可以指定一个值。 第二种形式(对象形式)由一个对象组成,其中每个动画 属性可以指定单个动画值或动画值数组。

第一种数组形式是规范形式,也是 getKeyframes() 方法返回的形式。

关键帧偏移可以 使用任一种形式指定,如下所示:

// 没有偏移的关键帧会自动计算偏移
// 第一个关键帧为 0,中间关键帧为 0.65,最后一个
// 关键帧为 1。
elem.animate([ { color: 'blue' },
               { color: 'green', offset: 0.5 },
               { color: 'red' },
               { color: 'yellow', offset: 0.8 },
               { color: 'pink' } ], 2000);

// 以下代码产生相同的结果。请注意,无需
// 指定最后一个值:它将自动被视为 'null',然后
// 自动分配将像前一种情况一样应用。
elem.animate({ color: [ 'blue', 'green', 'red', 'yellow', 'pink' ],
               offset: [ null, 0.5, null, 0.8 ] }, 2000);

同样,计时函数和关键帧特定合成 操作也可以用任一种形式指定。数组形式允许 为每个关键帧指定不同的值,而对于对象形式, 值列表会根据需要重复,直到每个关键帧都被 分配一个值。

// 由于计时函数应用在关键帧_之间_,即使我们在最后一个关键帧上指定
// 计时函数,它也会被忽略。
elem.animate([ { color: 'blue', easing: 'ease-in' },
               { color: 'green', easing: 'ease-out' },
               { color: 'yellow' } ], 2000);

// 以下代码产生相同的结果。
elem.animate({ color: [ 'blue', 'green', 'yellow' ],
               easing: [ 'ease-in', 'ease-out' ] }, 2000);

// 重复行为使得为所有关键帧分配相同的值
// 变得简单:
elem.animate({ color: [ 'blue', 'green', 'yellow' ],
               easing: 'ease-in-out' }, 2000);

请注意,任一种形式中的 easing 属性都设置关键帧特定计时 函数。 这独立于应用于 关键帧效果整个迭代 持续时间的计时 函数,后者使用 KeyframeEffectOptions 对象指定(或者在使用 Animatable 接口混入的 animate() 方法时使用 KeyframeAnimationOptions 对象)。

在以下示例中,两条语句产生不同的结果。

// 此处,'ease-in-out' 应用于每个颜色值之间。
elem.animate({ color: [ 'blue', 'green', 'yellow' ],
               easing: 'ease-in-out' }, 2000);

// 但是,在这种情况下,'ease-in-out' 应用于整个范围
// 的动画,也就是从 'blue' 到 'yellow'。
elem.animate({ color: [ 'blue', 'green', 'yellow' ] },
             { duration: 2000, easing: 'ease-in-out' });

keyframes 参数的类型无法用 WebIDL 表达,因为它依赖于一种部分开放的字典类型。

从概念上讲,此参数的类型等价于以下 类 WebIDL 定义:

dictionary Keyframe {
    // ... 属性-值对 ...
    // 即 DOMString propertyName
    double?                   offset = null;
    DOMString                 easing = "linear";
    CompositeOperationOrAuto  composite = "auto";
};

dictionary PropertyIndexedKeyframes {
    // ... 属性-值和属性-值列表对 ...
    // 即 (DOMString or sequence&lt;DOMString&gt;) propertyName
    (double? or sequence<double?>)                         offset = [];
    (DOMString or sequence<DOMString>)                     easing = [];
    (CompositeOperationOrAuto or sequence<CompositeOperationOrAuto>) composite = [];
};

typedef (sequence<Keyframe> or PropertyIndexedKeyframes) KeyframeArgument;

每个参数的含义和允许的值如下:

offset

关键帧的关键帧 偏移,指定为 0.0 到 1.0(含)之间的数字或 null。

null 值表示关键帧应自动在相邻 关键帧之间均匀分布。

指定 [0.0, 1.0] 范围之外的偏移会导致 抛出 TypeError。

指定了偏移的关键帧必须按偏移 递增顺序提供。不过,允许相邻且相等的偏移。

easing

用于变换从此关键帧到序列中下一个关键帧之间时间进度的计时函数。

解析此字符串时使用的语法和错误处理 与为 EffectTiming 接口的 easing 属性定义的完全相同。

composite

用于将此关键帧中指定的值 与底层值组合的关键帧特定合成 操作。

如果为 auto, 则使用关键帧 效果上指定的合成操作。

由于此类型无法用 WebIDL 表达,其处理过程在 下文以文字形式定义。

对于每个接受 keyframes 参数的方法,都会对输入运行 处理 keyframes 参数过程,并 保留该过程的结果。

首先定义两个辅助定义。

指令:检查 result 的完成记录, 其中 result 是调用 ECMAScript 操作得到的完成记录,等价于以下步骤:

  1. 如果 result 是突然完成,则抛出 result 的 [[value]] 字段中包含的异常,并中止此过程。

    如果 [[type]] 为 break、continue 或 return,我们应该怎么做?这种情况可能发生吗?

  2. 将 result 替换为 result 的 [[value]] 字段中包含的值。

处理类关键帧对象的过程接受两个 参数:

并且,如果 allow lists 为 false,则返回从属性名称到 DOMString 值的映射;否则返回 从属性名称到 DOMString 值序列的映射,具体使用以下过程:

  1. 运行将 ECMAScript 值转换为字典类型的过程 [WEBIDL], 其中 ECMAScript 值为 keyframe input,字典类型取决于 allow lists 标志的值,如下所示:

    如果 allow lists 为 true,

    使用以下字典类型:

    dictionary BasePropertyIndexedKeyframe {
        (double? or sequence<double?>)                         offset = [];
        (DOMString or sequence<DOMString>)                     easing = [];
        (CompositeOperationOrAuto or sequence<CompositeOperationOrAuto>) composite = [];
    };
    
    否则,

    使用以下字典类型,

    dictionary BaseKeyframe {
        double?                  offset = null;
        DOMString                easing = "linear";
        CompositeOperationOrAuto composite = "auto";
    };
    

    将此过程的结果存储为 keyframe output。

  2. 按如下方式构建 animatable properties 列表:

    1. 令 animatable properties 为实现能够进行动画处理的属性 名称列表(包括具有可动画普通子属性的简写属性)。

    2. 通过应用动画属性名称到 IDL 属性名称算法,将 animatable properties 中的每个属性名称转换为等效的 IDL 属性。

  3. 令 input properties 为以 keyframe input 作为对象调用 EnumerableOwnNames 操作所得的结果。

  4. 创建一个新的列表 animation properties,其中包含 同时位于 input properties 和 animatable properties 中的所有属性,或者位于 input properties 中并符合 <custom-property-name> 产生式的属性。

  5. 按照定义每个属性名称的 Unicode 码点升序对 animation properties 排序。

  6. 对于 animation properties 中的每个 property name,

    1. 令 raw value 为在 keyframe input 上调用 [[Get]] 内部方法所得的结果,其中 property name 作为属性键,keyframe input 作为 receiver。

    2. 检查 raw value 的完成记录。

    3. 按如下方式将 raw value 转换为 DOMString 或 DOMString 序列 property values:

      如果 allow lists 为 true,

      令 property values 为使用将 ECMAScript 值转换为 IDL 值所定义的 过程 [WEBIDL],将 raw value 转换为 IDL 类型 (DOMString or sequence<DOMString>) 所得的结果。

      如果 property values 是单个 DOMString,则用 仅以 property values 原始值作为唯一元素的 DOMString 序列替换 property values。

      否则,

      令 property values 为使用将 ECMAScript 值转换为 DOMString 的过程 [WEBIDL],将 raw value 转换为 DOMString 所得的结果。

    4. 令 normalized property name 为将IDL 属性名称到 动画属性名称算法应用于 property name 所得的结果。

    5. 向 keyframe output 添加一个属性,以 normalized property name 作为属性名称,以 property values 作为属性 值。

  7. 返回 keyframe output。

处理 keyframes 参数的过程接受一个可为空的 ECMAScript 对象 object 作为输入,并使用以下过程返回 一个关键帧序列:

  1. 如果 object 为 null,则返回一个空的关键帧序列。

  2. 令 processed keyframes 为一个空的关键帧序列。

  3. 令 method 为 GetMethod(object, @@iterator) 所得的结果。

  4. 检查 method 的完成记录。

  5. 执行以下第一个匹配条件所对应的步骤,

    如果 method 不是 undefined,
    1. 令 iter 为 GetIterator(object, method)。

    2. 检查 iter 的完成记录。

    3. 重复:

      1. 令 next 为 IteratorStep(iter)。

      2. 检查 next 的完成记录。

      3. 如果 next 为 false,则中止此循环。

      4. 令 nextItem 为 IteratorValue(next)。

      5. 检查 nextItem 的完成记录。

      6. 如果 Type(nextItem) 不是 Undefined、Null 或 Object,则 抛出 TypeError 并中止 这些步骤。

      7. 运行处理类关键帧 对象过程,将 nextItem 作为 keyframe input 传入,并将 allow lists 标志设置为 false,然后将所得结果附加到 processed keyframes。

    否则,
    1. 令 property-indexed keyframe 为 运行处理类关键帧 对象过程所得的结果,将 object 作为 keyframe input 传入,并 将 allow lists 标志设置为 true。

    2. 对于 property-indexed keyframe 中的每个成员 m,执行以下步骤:

      1. 令 property name 为 m 的键。

      2. 如果 property name 为 "composite"、"easing" 或 "offset",则跳过 此循环中的剩余步骤,并从 property-indexed keyframe 中 m 之后的下一个成员继续。

      3. 令 property values 为 m 的值。

      4. 令 property keyframes 为一个空的关键帧序列。

      5. 对于 property values 中的每个值 v,执行以下 步骤:

        1. 令 k 为一个新的关键帧,其关键帧偏移为 null。

        2. 将属性-值对 property name → v 添加到 k。

        3. 将 k 附加到 property keyframes。

      6. 将计算缺失 关键帧 偏移过程应用于 property keyframes。

      7. 将 property keyframes 中的关键帧添加到 processed keyframes。

    3. 按每个关键帧的已计算 关键帧偏移升序对 processed keyframes 排序。

    4. 当 processed keyframes 中相邻关键帧具有相等的已计算关键帧偏移时,将它们合并。

    5. 令 offsets 为一个可为空的 double 值序列, 根据 property-indexed keyframe 的 "offset" 成员类型按如下方式赋值:

      sequence<double?>,

      原样使用 "offset" 的值。

      double?,

      一个长度为一的序列,以 "offset" 的值作为其唯一项,即 « offset »,

    6. 依次将 offsets 中的每个值赋给 processed keyframes 中对应位置关键帧 偏移的关键帧,直到任一序列到达末尾。

    7. 令 easings 为一个 DOMString 值序列,根据 property-indexed keyframe 的 "easing" 成员 类型按如下方式赋值:

      sequence<DOMString>,

      原样使用 "easing" 的值。

      DOMString,

      一个长度为一的序列,以 "easing" 的值作为其唯一项,即 « easing »,

    8. 如果 easings 是空序列,则令其为长度为一、 包含单个值 "linear" 的序列,即 « "linear" »。

    9. 如果 easings 的项目数少于 processed keyframes,则从列表开头依次重复 easings 中的元素, 直到 easings 的项目数与 processed keyframes 相同。

      例如,如果 processed keyframes 有五个项目,而 easings 是序列 « "ease-in", "ease-out" »,则 easings 会重复为 « "ease-in", "ease-out", "ease-in", "ease-out", "ease-in" »。
    10. 如果 easings 的项目数多于 processed keyframes,则将多余项目存储为 unused easings。

    11. 依次将 easings 中的每个值赋给 processed keyframes 中对应位置关键帧上名为 "easing" 的属性,直到到达 processed keyframes 的末尾。

    12. 如果 property-indexed keyframe 的 "composite" 成员不是空序列:

      1. 令 composite modes 为一个 CompositeOperationOrAuto 值序列,从 property-indexed keyframe 的 "composite" 成员赋值。 如果该成员是单个 CompositeOperationOrAuto 值 操作,则令 composite modes 为长度为 一的序列,并以 "composite" 的值作为其 唯一项。

      2. 与 easings 一样,如果 composite modes 的项目数少于 processed keyframes,则从列表开头依次重复 composite modes 中的元素,直到 composite modes 的项目数与 processed keyframes 相同。

      3. 依次将 composite modes 中每个不是 auto 的值赋给 processed keyframes 中对应位置关键帧上的关键帧特定 合成操作,直到到达 processed keyframes 的末尾。

  6. 如果 processed keyframes 没有按 偏移宽松排序,则抛出一个 TypeError 并中止 这些步骤。

  7. 如果 processed keyframes 中存在任何关键帧, 其关键帧偏移非 null 且小于 零或大于一,则抛出一个 TypeError 并中止这些步骤。

  8. 对于 processed keyframes 中的每个 frame,执行 以下步骤:

    1. 对于 frame 中的每个属性-值对,使用为该属性指定的 语法解析属性值。

      如果根据该属性的语法,属性值无效,则丢弃该属性-值对。 为诊断内容中的错误提供支持的用户代理 应当生成适当的警告,突出显示无效的 属性值。

    2. 令 frame 的计时函数为使用为 EffectTiming 字典的 easing 成员定义的 CSS 语法解析 frame 上的 "easing" 属性所得的结果。

      如果解析 "easing" 属性失败,则抛出一个 TypeError 并中止此过程。

      注意: 在上述两个步骤中都使用 CSS 解析器 意味着允许 CSS 注释和转义,但成功解析值后不会保留它们。

      注意: 在 "easing" 属性解析失败的情况下, 很重要的一点是,必须在读取完 object 的所有 属性之后才抛出 TypeError,因为若不这样做 将是可观察的,并且与 WebIDL 将来支持部分 开放字典时的行为不匹配。

  9. 使用为 EffectTiming 接口的 easing 成员定义的 CSS 语法解析 unused easings 中的每个值,如果任何值解析失败,则抛出一个 TypeError 并中止此过程。

    为了提供一致的行为,使得在以下 所有情况下都会抛出 TypeError,最后这一步是必需的:

    elem.animate({ easing: 'invalid' });
    elem.animate({ easing: ['invalid'] });
    elem.animate([{ easing: 'invalid' }]);
    

6.6.4. KeyframeEffectOptions 字典

可以通过提供 KeyframeEffectOptions 对象,向 KeyframeEffect(target, keyframes, options) 构造函数传递额外参数。

dictionary KeyframeEffectOptions : EffectTiming {
    CompositeOperation composite = "replace";
    CSSOMString?       pseudoElement = null;
};
composite, 类型为 CompositeOperation,默认为 "replace"

用于将此动画与效果栈合成的合成操作,由 CompositeOperation 枚举值之一指定。 这用于所有指定 auto 关键帧特定合成 操作的关键帧。

pseudoElement, 类型为 CSSOMString,可为空,默认为 null

伪元素选择器(必须有效或为 null),用于根据目标元素指定效果目标。

6.7. CompositeOperation 和 CompositeOperationOrAuto 枚举

关键帧效果的 合成行为的可能值由 CompositeOperation 枚举表示。

enum CompositeOperation { "replace", "add", "accumulate" };
replace

对应于 replace 合成操作值, 使得 动画 效果会覆盖与其组合的底层值。

add

对应于 add 合成操作值, 使得 动画 效果会相加到与其组合的底层值上。

accumulate

对应于 accumulate 合成操作值, 使得 动画 效果会累积到底层值上。

关键帧的 合成行为的可能值与 CompositeOperation 枚举具有相同的值,并额外包含 auto 值。

enum CompositeOperationOrAuto { "replace", "add", "accumulate", "auto" };
auto

表示应使用关联关键帧 效果的合成操作。

6.8. Animatable 接口混入

可以作为 KeyframeEffect 对象目标的对象实现 Animatable 接口混入。

interface mixin Animatable {
    Animation           animate(object? keyframes,
                                optional (unrestricted double or KeyframeAnimationOptions) options = {});
    sequence<Animation> getAnimations(optional GetAnimationsOptions options = {});
};

dictionary KeyframeAnimationOptions : KeyframeEffectOptions {
    DOMString id = "";
    AnimationTimeline? timeline;
};

dictionary GetAnimationsOptions {
    boolean subtree = false;
};
Animation animate(keyframes, options)

执行以下步骤:

  1. 令 target 为调用此方法的对象。

  2. 在 target 的相关 Realm中构造一个新的 KeyframeEffect 对象 effect, 使用与 KeyframeEffect(target, keyframes, options) 构造函数相同的 过程,将 target 作为 target 参数传入,并按提供的值传入 keyframes 和 options 参数。

    如果上述过程导致抛出异常,则传播该 异常并中止此过程。

  3. 如果 options 是一个 KeyframeAnimationOptions 对象,则令 timeline 为 options 的 timeline 成员;或者,如果 options 缺少 timeline 成员, 则为调用此 方法的元素的节点文档的默认文档时间线。

  4. 在 target 的相关 Realm中构造一个新的 Animation 对象 animation,使用 与 Animation() 构造函数相同的过程,将 effect 和 timeline 作为同名参数传入。

  5. 如果 options 是一个 KeyframeAnimationOptions 对象, 则将 options 的 id 成员的值赋给 animation 的 id 属性。

  6. 针对 animation 运行播放动画过程,并将 auto-rewind 标志设置为 true。

  7. 返回 animation。

本节为非规范性内容

以下代码片段:

var animation = elem.animate({ opacity: 0 }, 2000);

大致等价于:

var effect = new KeyframeEffect(elem, { opacity: 0 }, 2000);
var animation = new Animation(effect, elem.ownerDocument.timeline);
animation.play();
keyframes

要使用的关键帧。 此值作为 keyframes 参数传递给 KeyframeEffect(target, keyframes, options) 构造函数,并具有 与该构造函数所定义的相同解释。

options

所创建的 KeyframeEffect 和 Animation 的计时和动画选项。

sequence<Animation> getAnimations(options)

返回此对象的相关动画集合;或者, 如果传入一个 options 参数,并将 subtree 设置为 true, 则返回此对象的子树的相关动画集合。

返回的列表使用 § 5.4.2 效果栈中为效果的关联动画描述的合成顺序进行排序。

调用此方法会为目标 元素触发样式更改事件。 因此,返回的列表反映应用任何 待处理动画样式更改之后的状态,例如尚未处理的与动画相关的 样式属性更改。

options

控制 getAnimations() 返回的动画集合的参数。

id, 类型为 DOMString,默认为 ""

要分配给生成的 Animation 的 id 属性的字符串。

timeline, 类型为 AnimationTimeline,可为空

一个可选值,如果存在,则指定要与新创建的动画关联的时间线。

subtree, 类型为 boolean,默认为 false

如果为 true,则表示与动画效果关联的动画也应 包含在结果中,其中该动画效果的目标元素是调用 getAnimations() 的对象的后代。

6.9. Document 接口的扩展

对 [DOM] 中定义的 Document 接口作出以下扩展。

partial interface Document {
    readonly attribute DocumentTimeline timeline;
};
timeline, 类型为 DocumentTimeline,只读

表示 默认文档时间线的 DocumentTimeline 对象。

6.10. DocumentOrShadowRoot 接口混入的扩展

对 [DOM] 中定义的 DocumentOrShadowRoot 接口 混入作出以下扩展。

partial interface mixin DocumentOrShadowRoot {
    sequence<Animation> getAnimations();
};
sequence<Animation> getAnimations()

返回调用此方法的文档或影子根的子树的相关动画集合。

返回的列表使用 § 5.4.2 效果栈中为效果的关联动画描述的合成顺序进行排序。

调用此方法会为文档触发样式更改事件。 因此,返回的列表反映应用任何 待处理动画样式更改之后的状态,例如尚未处理的与动画相关的 样式属性更改。

6.11. Element 接口的扩展

由于 DOM 元素可以作为动画的目标, Element 接口 [DOM] 扩展如下:

Element includes Animatable;

这允许以下用法。

elem.animate({ color: 'red' }, 2000);

6.12. AnimationPlaybackEvent 接口

动画 播放事件使用 AnimationPlaybackEvent 接口表示。

[Exposed=Window]
interface AnimationPlaybackEvent : Event {
    constructor(DOMString type, optional AnimationPlaybackEventInit eventInitDict = {});
    readonly attribute double? currentTime;
    readonly attribute double? timelineTime;
};
dictionary AnimationPlaybackEventInit : EventInit {
    double? currentTime = null;
    double? timelineTime = null;
};
AnimationPlaybackEvent(type, eventInitDict)

使用 [DOM] 中为构造事件定义的过程 构造一个新的 AnimationPlaybackEvent 对象。

currentTime, 类型为 double,只读,可为空

在事件加入队列的时刻,生成该事件的动画的当前时间。 如果生成事件时该动画处于空闲状态,则此值为 null。

timelineTime, 类型为 double,只读,可为空

在事件加入队列的时刻,与生成该事件的动画关联的时间线的时间值。 如果事件加入队列时该动画未 与活动时间线关联,则此值为 null。

currentTime, 类型为 double,可为空,默认为 null

参见 currentTime 属性的说明。

timelineTime, 类型为 double,可为空,默认为 null

参见 timelineTime 属性的说明。

6.13. 模型活性

对模型任何部分所作的更改都会使整个计时模型 以及任何依赖样式得到更新。

除非另有说明, 调用本规范编程接口一节中定义的接口的方法或构造函数, 或获取或设置这些接口的成员 不会产生样式更改事件。

注意: 扩展本规范的其他规范预计会 通过引入会触发此类事件的情形来细化 对样式更改事件的要求。 例如, 当本规范中的接口表示 由 CSS 标记定义的动画时, 它们的许多方法都需要触发样式更改事件, 以反映指定样式的更改。

本节为非规范性内容

根据上述要求以及本规范其他部分的规范性要求, 可以观察到以下不变性:

对 Web 动画模型所作的更改会立即生效

例如,如果与 Animation 关联的 KeyframeEffect 通过编程接口被定位(参见§ 4.4.4 设置动画的当前 时间), 则查询动画的 startTime 时返回的值会立即反映 模型的更新状态。

// 最初 animation.effect.getComputedTiming().localTime 为 3000
animation.currentTime += 2000;
alert(animation.effect.getComputedTiming().localTime); // 显示 "5000"
查询受动画影响属性的计算样式 会返回动画完全最新的状态

例如,如果在向元素应用新的 Animation 后立即查询该元素的已用样式, 则新动画的结果会 包含在返回值中。

// 立即将 opacity 设置为 0
elem.animate({ opacity: 0 }, { fill: 'forwards' });
alert(window.getComputedStyle(elem).opacity); // 显示 "0"
在同一任务中进行的更改会同步, 从而使整组更改一起渲染

由于对模型的更改会立即生效, 再结合 ECMAScript 的运行至完成语义, 因此永远不应出现例如只渲染指定 样式的更改而没有 应用动画的情况。

// 淡出 opacity,并为不
// 支持 Element.animate 的浏览器提供回退
elem.style.opacity = '0';
elem.animate([ { opacity: 1 }, { opacity: 0 } ], 500);

注意,不过,在上述示例中,用户代理可能会 渲染一个上述更改均未应用的 帧。 例如,如果渲染发生在 一个单独的进程中,而该进程被安排在 上述任务完成后不久、但更改 尚未来得及传递给该进程之前运行,就可能发生这种情况。

文档 时间线的 currentTime 属性返回的值在任务内不会改变

由于要求时间线在每次运行更新动画并发送事件 过程时更新其当前时间, 因此在同一个脚本块中执行的一大段代码内 两次查询 currentTime 将返回相同的值,如以下示例所示。

var a = document.timeline.currentTime;
// ... 很多行代码 ...
var b = document.timeline.currentTime;
alert(b - a); // 显示 0
传递给 requestAnimationFrame 回调的时间 将等于 document.timeline.currentTime

由于 HTML 的事件循环处理模型定义了 更新动画并发送事件过程 在运行动画帧 回调之前执行,并且由于传递给这些回调的时间是传递给两个过程的同一个 now 时间戳,因此默认文档时间线的当前时间应当 与传递给 requestAnimationFrame 的时间匹配。

window.requestAnimationFrame(function(now) {
  // 显示 0
  alert(now - document.timeline.currentTime);
});
调用此编程接口中的方法通常不会导致 触发过渡

考虑以下示例:

// 设置过渡起点
div.style.opacity = '1';
getComputedStyle(div).opacity;

// 设置过渡终点
div.style.transition = 'opacity 1s';
div.style.opacity = '0';

// 触发动画
div.animate({ opacity: [0.5, 1] }, 500);

// 等待过渡结束——以下代码永远不会被调用!
div.addEventListener('transitionend', () => {
  console.log('transitionend');
});

在这种情况下,调用 animate() 不会触发样式更改事件。 因此,待处理的样式更改会与 新动画产生的样式更改同时处理。 由于动画样式会覆盖 更改前样式和更改后样式, 因此不会生成过渡,并且 transitionend 事件的事件处理程序永远不会被调用。

7. 与媒体片段的集成

媒体片段规范 [MEDIA-FRAGS] 定义了一种 对媒体资源的时间范围进行寻址的方法。 媒体片段的应用取决于 指定它们的资源的 MIME 类型。 对于具有 SVG MIME 类型 [SVG11] 的资源,时间参数的应用 在 动画元素 规范中定义。

注意: 媒体片段被定义为根据资源的 MIME 类型对资源进行操作。 因此,并非所有使用 Web 动画内容的情形 都支持时间寻址。

8. 与页面显示的交互

HTML 允许用户代理将用户代理定义的状态与 会话历史记录条目一起存储,以便用户在页面之间导航时, 可以恢复页面之前的状态,包括 滚动位置等状态 [HTML]。

当引用文档被卸载和遍历时,会暂停和恢复媒体元素的用户代理, 建议对包含 Web 动画内容的文档采用一致的处理方式。 如果提供此行为,应当通过调整跟踪挂钟时间的任何时间线的时间 值来实现。

这是否与这些 时间值相对于 navigationStart,以及 requestAnimationFrame 使用与 document.timeline.currentTime 相同的时间相冲突?[问题 #2083]

9. 实现要求

9.1. 时间值的精度

时间值的内部表示取决于实现, 但是,建议用户代理能够以微秒精度表示输入 时间值,以便名义上表示毫秒的时间值 0.001 能与 0.0 区分开来。

9.2. 一致性标准

本规范定义了一个动画抽象模型,因此 对于不支持脚本的用户代理,由于不存在 可测试的接口面,因此没有一致性标准。

但是,不支持脚本的用户代理可以实现 根据本规范定义的其他技术,在这种情况下, 本规范提供的定义将构成 该附加技术一致性标准的一部分。

一致的脚本化 Web 动画用户代理是实现§ 6 编程接口中定义的 API 的用户 代理。

10. 致谢

感谢 Steve Block、Michael Giuffrida、Ryan Seys 和 Eric Willigers 对本规范作出的贡献。

还要感谢 Michiel "Pomax" Kamermans 帮助推导 一个提议的平滑计时函数的方程,尽管此功能 已推迟到后续规范中。

我们衷心感谢 Southern Star Animation 的慷慨与耐心,他们向 编辑介绍了制作广播 动画所使用的流程和技术。

11. 自上次发布以来的变更

自 2022 年 9 月 8 日工作草案以来,进行了以下变更:

变更日志提供了更 详细的历史记录。

附录 A:现有 属性的动画类型

通常,属性的动画类型 会与其定义一起提供。 但是,对于在较旧或非常成熟的规范中定义的一些属性, 并未包含动画类型信息。 除非属于下面列出的例外,否则所有此类属性都被假定具有按计算值的动画类型。

font-weight 的动画处理

第 4 层级之前的 font-weight 属性值按如下方式组合:

注意: 此定义已被 [CSS-FONTS-4] 废弃,其中 取消了 font-weight 值必须是 100 的倍数的要求。 此时,font-weight 的动画 类型就是按计算值。

visibility 的动画处理

对于 visibility 属性,visible 作为离散步长进行插值, 其中 p 在 0 和 1 之间的值 映射到 visible,而 p 的其他值映射到更接近的端点; 如果两个值都不是 visible,则使用离散动画。

box-shadow 和 text-shadow 的动画处理

对 box-shadow 或 text-shadow 属性进行动画处理时,遵循如下组合阴影列表的过程:

列表中的每个阴影 (将 none 视为长度为 0 的列表) 都按分量进行插值,其行为与按计算值相同。 但是,如果两个输入阴影都是 inset,或者两个输入阴影都不是 inset, 则插值后的阴影在这方面必须与输入阴影一致。 如果任何一对输入阴影中一个为 inset 而另一个不是 inset, 则整个阴影列表使用离散 动画。 如果阴影列表的长度不同, 则较短的列表在末尾 用颜色为transparent、 所有长度均为 0, 且其 inset(或非 inset)与较长列表匹配的阴影进行填充。

两个阴影列表 Va 和 Vb 的相加定义为列表连接,使得 Vresult 等于 用 Vb 扩展后的 Va。

阴影列表的累积遵循上述 插值匹配规则,根据每个分量的类型执行相加, 或者如果 inset 值不匹配,则回退到离散动画。

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合来表达。本文档规范性部分中的关键词“MUST”、 “MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、 “RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的说明解释。 不过,为了提高可读性,这些词在本规范中并非全部以 大写字母出现。

除明确标记为非规范性的章节、示例和注释外, 本规范的所有文本均为规范性内容。 [RFC2119]

本规范中的示例以“例如”等词语引出, 或使用 class="example" 与规范性文本区分开来, 如下所示:

这是一个资料性示例。

资料性注释以“注意”一词开头,并使用 class="note" 与规范性文本区分开来,如下所示:

注意,这是一条资料性注释。

建议事项是采用特殊样式以引起特别注意的规范性章节,并使用 <strong class="advisement"> 与其他规范性文本区分开来,例如: 用户代理必须提供无障碍替代方案。

一致性类别

本规范的一致性 针对以下三种一致性类别定义:

样式表
一个 CSS 样式表。
渲染器
一个解释样式表语义并渲染 使用这些样式表的文档的 用户代理。
创作工具
一个编写样式表的 用户代理。

如果样式表中所有使用本模块所定义语法的语句, 均根据通用 CSS 语法以及本模块中定义的每项 功能各自的语法有效,则该样式表符合本规范。

如果渲染器除了按照 适用规范定义的方式解释样式表外,还能通过正确解析 本规范定义的所有功能并相应地渲染文档来支持这些功能, 则该渲染器符合本规范。不过,由于设备限制而导致 用户代理无法正确渲染文档,并不会使该用户代理不符合规范。 (例如,不要求用户代理在单色显示器上渲染颜色。)

如果创作工具编写的样式表在语法上符合 通用 CSS 语法以及本模块中各项功能的独立语法, 并满足本模块所述样式表的所有其他一致性要求, 则该创作工具符合本规范。

部分实现

为了使作者能够利用向前兼容的解析规则 分配回退值,CSS 渲染器必须将任何其不具有可用支持级别的 @ 规则、属性、属性值、关键词以及其他语法结构视为无效(并酌情忽略)。 特别是,用户代理不得在单个 多值属性声明中选择性地忽略不支持的分量值而采用支持的值: 如果任何值被视为无效 (不支持的值必须如此),CSS 要求忽略整个声明。

不稳定功能和 专有功能的实现

为了避免与未来稳定的 CSS 功能发生冲突, CSSWG 建议在实现 CSS 的不稳定功能和专有扩展时遵循最佳实践。

非实验性实现

规范一旦达到候选推荐标准阶段, 就可以进行非实验性实现,实现者应当 发布任何能够证明按照规范正确实现的 CR 级功能的 无前缀实现。

为了在各种实现之间建立并维持 CSS 的互操作性, CSS 工作组要求非实验性 CSS 渲染器在发布任何 CSS 功能的无前缀实现之前, 向 W3C 提交实现报告(以及必要时提交 该实现报告使用的测试用例)。提交给 W3C 的测试用例 须接受 CSS 工作组的审查和修正。

有关提交测试用例和实现报告的更多信息, 可以在 CSS 工作组网站 https://www.w3.org/Style/CSS/Test/ 上找到。 问题应发送到 public-css-testsuite@w3.org 邮件 列表。

索引

本规范定义的 术语

通过引用定义的 术语

参考文献

规范性参考文献

[CSS-ANIMATIONS-1]
David Baron; et al. CSS 动画第 1 级。2023 年 3 月 2 日。WD。URL:https://www.w3.org/TR/css-animations-1/
[CSS-ANIMATIONS-2]
David Baron; Brian Birtles. CSS 动画第 2 级。2023 年 3 月 2 日。WD。URL:https://www.w3.org/TR/css-animations-2/
[CSS-BACKGROUNDS-3]
Bert Bos; Elika Etemad; Brad Kemper. CSS 背景与边框模块第 3 级。2023 年 2 月 14 日。CR。URL:https://www.w3.org/TR/css-backgrounds-3/
[CSS-CASCADE-3]
Elika Etemad; Tab Atkins Jr.. CSS 层叠与 继承第 3 级。2021 年 2 月 11 日。REC。URL:https://www.w3.org/TR/css-cascade-3/
[CSS-CASCADE-5]
Elika Etemad; Miriam Suzanne; Tab Atkins Jr.. CSS 层叠与继承第 5 级。2022 年 1 月 13 日。CR。URL:https://www.w3.org/TR/css-cascade-5/
[CSS-COLOR-4]
Tab Atkins Jr.; Chris Lilley; Lea Verou. CSS 颜色模块 第 4 级。2022 年 11 月 1 日。CR。URL:https://www.w3.org/TR/css-color-4/
[CSS-DISPLAY-3]
Elika Etemad; Tab Atkins Jr.. CSS Display 模块第 3 级。2023 年 3 月 30 日。CR。URL:https://www.w3.org/TR/css-display-3/
[CSS-EASING-1]
Brian Birtles; Dean Jackson; Matt Rakow. CSS 缓动 函数第 1 级。2023 年 2 月 13 日。CR。URL:https://www.w3.org/TR/css-easing-1/
[CSS-FONTS-4]
John Daggett; Myles Maxfield; Chris Lilley. CSS 字体 模块第 4 级。2021 年 12 月 21 日。WD。URL:https://www.w3.org/TR/css-fonts-4/
[CSS-LOGICAL-1]
Rossen Atanassov; Elika Etemad. CSS 逻辑属性 与值第 1 级。2018 年 8 月 27 日。WD。URL:https://www.w3.org/TR/css-logical-1/
[CSS-PROPERTIES-VALUES-API-1]
Tab Atkins Jr.; et al. CSS 属性与 值 API 第 1 级。2020 年 10 月 13 日。WD。URL:https://www.w3.org/TR/css-properties-values-api-1/
[CSS-SHADOW-PARTS-1]
Tab Atkins Jr.; Fergal Daly. CSS 影子 部件。2018 年 11 月 15 日。WD。URL:https://www.w3.org/TR/css-shadow-parts-1/
[CSS-STYLE-ATTR]
Tantek Çelik; Elika Etemad. CSS 样式 属性。2013 年 11 月 7 日。REC。URL:https://www.w3.org/TR/css-style-attr/
[CSS-TEXT-DECOR-4]
Elika Etemad; Koji Ishii. CSS 文本装饰模块 第 4 级。2022 年 5 月 4 日。WD。URL:https://www.w3.org/TR/css-text-decor-4/
[CSS-TRANSFORMS-1]
Simon Fraser; et al. CSS 变换模块第 1 级。2019 年 2 月 14 日。CR。URL:https://www.w3.org/TR/css-transforms-1/
[CSS-TRANSITIONS-1]
David Baron; et al. CSS 过渡。2018 年 10 月 11 日。WD。URL:https://www.w3.org/TR/css-transitions-1/
[CSS-TRANSITIONS-2]
CSS 过渡第 2 级 URL:https://drafts.csswg.org/css-transitions-2/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS 值与单位 模块第 4 级。2023 年 4 月 6 日。WD。URL:https://www.w3.org/TR/css-values-4/
[CSS-VARIABLES-1]
Tab Atkins Jr.. 用于层叠 变量的 CSS 自定义属性模块第 1 级。2022 年 6 月 16 日。CR。URL:https://www.w3.org/TR/css-variables-1/
[CSS-WILL-CHANGE-1]
Tab Atkins Jr.. CSS Will Change 模块第 1 级。2022 年 5 月 5 日。CR。URL:https://www.w3.org/TR/css-will-change-1/
[CSS-WRITING-MODES-3]
Elika Etemad; Koji Ishii. CSS 书写模式第 3 级。2019 年 12 月 10 日。REC。URL:https://www.w3.org/TR/css-writing-modes-3/
[CSS-WRITING-MODES-4]
Elika Etemad; Koji Ishii. CSS 书写模式第 4 级。2019 年 7 月 30 日。CR。URL:https://www.w3.org/TR/css-writing-modes-4/
[CSS21]
Bert Bos; et al. 层叠样式表第 2 级修订版 1(CSS 2.1)规范。2011 年 6 月 7 日。REC。URL:https://www.w3.org/TR/CSS21/
[CSS22]
Bert Bos. 层叠样式表第 2 级修订版 2(CSS 2.2) 规范。2016 年 4 月 12 日。WD。URL:https://www.w3.org/TR/CSS22/
[CSSOM]
Daniel Glazman; Emilio Cobos Álvarez. CSS 对象模型 (CSSOM)。2021 年 8 月 26 日。WD。URL:https://www.w3.org/TR/cssom-1/
[DOM]
Anne van Kesteren. DOM 标准。现行标准。 URL:https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript 语言规范。URL:https://tc39.es/ecma262/multipage/
[HR-TIME]
Yoav Weiss. 高精度时间。2023 年 4 月 25 日。 WD。URL:https://www.w3.org/TR/hr-time-3/
[HTML]
Anne van Kesteren; et al. HTML 标准。 现行标准。URL:https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准。现行标准。URL:https://infra.spec.whatwg.org/
[MEDIA-FRAGS]
Raphaël Troncy; et al. 媒体片段 URI 1.0 (基础)。2012 年 9 月 25 日。REC。URL:https://www.w3.org/TR/media-frags/
[MOTION-1]
Dirk Schulze; et al. 运动路径模块第 1 级。 2018 年 12 月 18 日。WD。URL:https://www.w3.org/TR/motion-1/
[RFC2119]
S. Bradner. 用于 RFC 中 表示要求级别的关键词。1997 年 3 月。当前最佳实践。URL:https://datatracker.ietf.org/doc/html/rfc2119
[SELECT]
Tantek Çelik; et al. 选择器第 3 级。2018 年 11 月 6 日。REC。URL:https://www.w3.org/TR/selectors-3/
[SELECTORS-4]
Elika Etemad; Tab Atkins Jr.. 选择器第 4 级。2022 年 11 月 11 日。WD。URL:https://www.w3.org/TR/selectors-4/
[SVG11]
Erik Dahlström; et al. 可缩放矢量图形(SVG)1.1 (第二版)。2011 年 8 月 16 日。REC。URL:https://www.w3.org/TR/SVG11/
[SVG2]
Amelia Bellamy-Royds; et al. 可缩放矢量图形(SVG) 2。2018 年 10 月 4 日。CR。URL:https://www.w3.org/TR/SVG2/
[WEB-ANIMATIONS-2]
Brian Birtles; Robert Flack. Web 动画第 2 级。2023 年 2 月 21 日。WD。URL:https://www.w3.org/TR/web-animations-2/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准。现行 标准。URL:https://webidl.spec.whatwg.org/

资料性参考文献

[SMIL-ANIMATION]
Patrick Schmitz; Aaron Cohen. SMIL 动画。2001 年 9 月 4 日。REC。URL:https://www.w3.org/TR/smil-animation/

IDL 索引

[Exposed=Window]
interface AnimationTimeline {
    readonly attribute double? currentTime;
};

dictionary DocumentTimelineOptions {
  DOMHighResTimeStamp originTime = 0;
};

[Exposed=Window]
interface DocumentTimeline : AnimationTimeline {
  constructor(optional DocumentTimelineOptions options = {});
};

[Exposed=Window]
interface Animation : EventTarget {
    constructor(optional AnimationEffect? effect = null,
                optional AnimationTimeline? timeline);
             attribute DOMString                id;
             attribute AnimationEffect?         effect;
             attribute AnimationTimeline?       timeline;
             attribute double?                  startTime;
             attribute double?                  currentTime;
             attribute double                   playbackRate;
    readonly attribute AnimationPlayState       playState;
    readonly attribute AnimationReplaceState    replaceState;
    readonly attribute boolean                  pending;
    readonly attribute Promise<Animation>       ready;
    readonly attribute Promise<Animation>       finished;
             attribute EventHandler             onfinish;
             attribute EventHandler             oncancel;
             attribute EventHandler             onremove;
    undefined cancel();
    undefined finish();
    undefined play();
    undefined pause();
    undefined updatePlaybackRate(double playbackRate);
    undefined reverse();
    undefined persist();
    [CEReactions]
    undefined commitStyles();
};

enum AnimationPlayState { "idle", "running", "paused", "finished" };

enum AnimationReplaceState { "active", "removed", "persisted" };

[Exposed=Window]
interface AnimationEffect {
    EffectTiming         getTiming();
    ComputedEffectTiming getComputedTiming();
    undefined            updateTiming(optional OptionalEffectTiming timing = {});
};

dictionary EffectTiming {
    double                             delay = 0;
    double                             endDelay = 0;
    FillMode                           fill = "auto";
    double                             iterationStart = 0.0;
    unrestricted double                iterations = 1.0;
    (unrestricted double or DOMString) duration = "auto";
    PlaybackDirection                  direction = "normal";
    DOMString                          easing = "linear";
};

dictionary OptionalEffectTiming {
    double                             delay;
    double                             endDelay;
    FillMode                           fill;
    double                             iterationStart;
    unrestricted double                iterations;
    (unrestricted double or DOMString) duration;
    PlaybackDirection                  direction;
    DOMString                          easing;
};

enum FillMode { "none", "forwards", "backwards", "both", "auto" };

enum PlaybackDirection { "normal", "reverse", "alternate", "alternate-reverse" };

dictionary ComputedEffectTiming : EffectTiming {
    unrestricted double  endTime;
    unrestricted double  activeDuration;
    double?              localTime;
    double?              progress;
    unrestricted double? currentIteration;
};

[Exposed=Window]
interface KeyframeEffect : AnimationEffect {
    constructor(Element? target,
                object? keyframes,
                optional (unrestricted double or KeyframeEffectOptions) options = {});
    constructor(KeyframeEffect source);
    attribute Element?           target;
    attribute CSSOMString?       pseudoElement;
    attribute CompositeOperation composite;
    sequence<object> getKeyframes();
    undefined        setKeyframes(object? keyframes);
};

dictionary BaseComputedKeyframe {
     double?                  offset = null;
     double                   computedOffset;
     DOMString                easing = "linear";
     CompositeOperationOrAuto composite = "auto";
};

dictionary BasePropertyIndexedKeyframe {
    (double? or sequence<double?>)                         offset = [];
    (DOMString or sequence<DOMString>)                     easing = [];
    (CompositeOperationOrAuto or sequence<CompositeOperationOrAuto>) composite = [];
};

dictionary BaseKeyframe {
    double?                  offset = null;
    DOMString                easing = "linear";
    CompositeOperationOrAuto composite = "auto";
};

dictionary KeyframeEffectOptions : EffectTiming {
    CompositeOperation composite = "replace";
    CSSOMString?       pseudoElement = null;
};

enum CompositeOperation { "replace", "add", "accumulate" };

enum CompositeOperationOrAuto { "replace", "add", "accumulate", "auto" };

interface mixin Animatable {
    Animation           animate(object? keyframes,
                                optional (unrestricted double or KeyframeAnimationOptions) options = {});
    sequence<Animation> getAnimations(optional GetAnimationsOptions options = {});
};

dictionary KeyframeAnimationOptions : KeyframeEffectOptions {
    DOMString id = "";
    AnimationTimeline? timeline;
};

dictionary GetAnimationsOptions {
    boolean subtree = false;
};

partial interface Document {
    readonly attribute DocumentTimeline timeline;
};

partial interface mixin DocumentOrShadowRoot {
    sequence<Animation> getAnimations();
};

Element includes Animatable;

[Exposed=Window]
interface AnimationPlaybackEvent : Event {
    constructor(DOMString type, optional AnimationPlaybackEventInit eventInitDict = {});
    readonly attribute double? currentTime;
    readonly attribute double? timelineTime;
};
dictionary AnimationPlaybackEventInit : EventInit {
    double? currentTime = null;
    double? timelineTime = null;
};

问题索引

一定有比“原点时间”更好的术语—— 它与“时间原点”太相似了。 [问题 #2079] ↵
在存在某些计时函数时,动画效果的输入迭代 进度不限于 [0, 1] 范围。 但是,目前关键帧偏移确实被限制在 [0, 1] 范围内,而对于此范围之外的输入 迭代进度值,属性值只是简单地进行外推。

我们曾考虑移除此限制,因为存在一些情况, 能够在 [0, 1] 范围之外的迭代进度值处指定属性值的 非线性变化是有用的。 一个示例是从绿色插值到黄色的动画, 但它具有一个过冲计时函数,使其在回落到 黄色之前暂时插值“超过”黄色而达到红色。

虽然可以通过修改关键帧 和计时函数来实现此效果,但这种方法似乎破坏了模型中 计时关注点与动画效果之间的分离。

尚不清楚应如何实现此效果,但我们注意到, 允许关键帧偏移超出 [0, 1] 可能会使当前 指定的、根据需要合成偏移 0 和 1 处关键帧的行为 变得不一致。

参见东京 2013 F2F 会议记录讨论的第 4 节([0, 1] 范围之外的关键帧偏移)。

<https://github.com/w3c/csswg-drafts/issues/2081>

↵

关于 正在渲染 [HTML] 的定义,在涉及 display: contents 时仍在讨论中。 对于此过程,我们假定一个具有 display: contents 且原本会具有 相关布局框的元素(即它是已连接的,并且不属于 display: none 子树)正在渲染。

↵
remove() 方法可用于从其父组 或动画中移除效果。我们是否应该将其保留在第 1 级中,并 简单定义为从动画中移除动画效果? [问题 #2082] ↵
如果 [[type]] 为 break、continue 或 return,我们应该怎么做?这种情况可能发生吗? ↵
这是否与这些时间值相对于 navigationStart,以及 requestAnimationFrame 使用与 document.timeline.currentTime 相同的时间相冲突? [问题 #2083] ↵