Window:requestAnimationFrame() 方法
window.requestAnimationFrame() 方法告知浏览器你希望执行一个动画。它请求浏览器在下一次重绘之前调用一个用户提供的回调函数。
回调函数的调用频率通常会与显示器的刷新率匹配。最常见的刷新率是 60hz (每秒 60 次循环/帧),不过 75hz、120hz 和 144hz 也被广泛使用。为了提高性能和延长电池寿命,当 requestAnimationFrame() 在后台标签页或隐藏的 <iframe> 中运行时,在大多数浏览器中会暂停。
注意:如果希望动画更多帧,你的回调函数必须再次调用 requestAnimationFrame()。requestAnimationFrame() 是一次性的。
警告:请务必始终使用第一个参数(或获取当前时间的其他方法)来计算动画在一帧中将进展多少——否则,动画在高刷新率屏幕上会运行得更快。有关如何执行此操作的方法,请参见下面的示例。
语法
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)。
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() 和首次调用回调函数之间存在多帧延迟。
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.currentTime 与 timestamp 参数对齐,因此零值等同于第 0 帧的时间戳。
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() 进行动画,而不是回调的时间戳值。你可能使用此方法来实现稍微更高的同步精度,尽管额外的精度程度是可变的,并且提升不大。
注意:此示例不允许你可靠地同步动画回调。
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 |
浏览器兼容性
加载中…