Window:requestAnimationFrame() 方法

Baseline 已广泛支持

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

window.requestAnimationFrame() 方法告知浏览器你希望执行一个动画。它请求浏览器在下一次重绘之前调用一个用户提供的回调函数。

回调函数的调用频率通常会与显示器的刷新率匹配。最常见的刷新率是 60hz (每秒 60 次循环/帧),不过 75hz、120hz 和 144hz 也被广泛使用。为了提高性能和延长电池寿命,当 requestAnimationFrame() 在后台标签页或隐藏的 <iframe> 中运行时,在大多数浏览器中会暂停。

注意:如果希望动画更多帧,你的回调函数必须再次调用 requestAnimationFrame()requestAnimationFrame() 是一次性的。

警告:请务必始终使用第一个参数(或获取当前时间的其他方法)来计算动画在一帧中将进展多少——否则,动画在高刷新率屏幕上会运行得更快。有关如何执行此操作的方法,请参见下面的示例。

语法

js
requestAnimationFrame(callback)

参数

回调

当需要为下一次重绘更新动画时调用的函数。此回调函数将传递一个参数。

时间戳

一个 DOMHighResTimeStamp,指示前一帧渲染的结束时间(基于自 时间原点以来的毫秒数)。时间戳是一个十进制数字,以毫秒为单位,但最小精度为 1 毫秒。对于 Window 对象(而不是 Workers),它等于 document.timeline.currentTime。此时间戳在同一代理上运行的所有窗口(所有同源窗口,更重要的是,同源 iframe)之间共享——这允许在多个 requestAnimationFrame 回调之间同步动画。时间戳值也类似于在回调函数开始时调用 performance.now(),但它们绝不是相同的值。

当由 requestAnimationFrame() 排队等待的多个回调在单个帧中开始触发时,即使在计算每个先前回调的工作负载期间时间已经过去,每个回调也会收到相同的时间戳。

返回值

一个 unsigned long 整数值,即请求 ID,它唯一地标识回调列表中的条目。你不应该对其值做任何假设。你可以将此值传递给 window.cancelAnimationFrame() 以取消刷新回调请求。

警告:请求 ID 通常实现为每个窗口递增的计数器。因此,即使它从 1 开始计数,它也可能溢出并最终达到 0。虽然不太可能对短期应用程序造成问题,但你应该避免使用 0 作为无效请求标识符 ID 的哨兵值,而应选择不可达的值,例如 null。规范没有指定溢出行为,因此浏览器具有不同的行为。当溢出时,值将要么回绕到 0,要么回绕到负值,要么失败并出现错误。除非溢出抛出错误,否则请求 ID 也不是真正唯一的,因为对于无限多的回调,只有有限多个 32 位整数。然而,请注意,在 60Hz 渲染下,每帧调用 requestAnimationFrame() 100 次,大约需要 500 天才能达到问题。

示例

在此示例中,一个元素动画 2 秒(2000 毫秒)。该元素以 0.1px/ms 的速度向右移动,因此其相对位置(以 CSS 像素为单位)可以根据动画开始以来经过的时间(以毫秒为单位)通过 0.1 * elapsed 计算。该元素的最终位置在其初始位置右侧 200px (0.1 * 2000)。

js
const element = document.getElementById("some-element-you-want-to-animate");
let start;

function step(timestamp) {
  if (start === undefined) {
    start = timestamp;
  }
  const elapsed = timestamp - start;

  // Math.min() is used here to make sure the element stops at exactly 200px
  const shift = Math.min(0.1 * elapsed, 200);
  element.style.transform = `translateX(${shift}px)`;
  if (shift < 200) {
    requestAnimationFrame(step);
  }
}

requestAnimationFrame(step);

以下三个示例说明了设置时间零点(用于计算动画在每帧中进度的基线)的不同方法。如果你希望与外部时钟同步,例如 BaseAudioContext.currentTime,可用的最高精度是单个帧的持续时间,在 60Hz 下为 16.67ms。回调的时间戳参数表示前一帧的结束,因此你新计算的值最早将在下一帧中渲染。

此示例等待直到第一次回调执行才设置 zero。如果你的动画在开始时跳到新值,你必须以这种方式构造它。如果你不需要与任何外部事物(例如音频)同步,则建议使用此方法,因为某些浏览器在首次调用 requestAnimationFrame() 和首次调用回调函数之间存在多帧延迟。

js
let zero;
requestAnimationFrame(firstFrame);
function firstFrame(timestamp) {
  zero = timestamp;
  animate(timestamp);
}
function animate(timestamp) {
  const value = (timestamp - zero) / duration;
  if (value < 1) {
    element.style.opacity = value;
    requestAnimationFrame((t) => animate(t));
  } else element.style.opacity = 1;
}

此示例使用 document.timeline.currentTime 在首次调用 requestAnimationFrame 之前设置零值。document.timeline.currentTimetimestamp 参数对齐,因此零值等同于第 0 帧的时间戳。

js
const zero = document.timeline.currentTime;
requestAnimationFrame(animate);
function animate(timestamp) {
  const value = (timestamp - zero) / duration; // animation-timing-function: linear
  if (value < 1) {
    element.style.opacity = value;
    requestAnimationFrame((t) => animate(t));
  } else element.style.opacity = 1;
}

此示例使用 performance.now() 进行动画,而不是回调的时间戳值。你可能使用此方法来实现稍微更高的同步精度,尽管额外的精度程度是可变的,并且提升不大。

注意:此示例不允许你可靠地同步动画回调。

js
const zero = performance.now();
requestAnimationFrame(animate);
function animate() {
  const value = (performance.now() - zero) / duration;
  if (value < 1) {
    element.style.opacity = value;
    requestAnimationFrame((t) => animate(t));
  } else element.style.opacity = 1;
}

规范

规范
HTML
# dom-animationframeprovider-requestanimationframe

浏览器兼容性

另见