动画 DOM 元素[1] 或 画布 的内容是 setInterval 的经典用例。但间隔并不像看起来那样可靠,现在可以使用更合适的 API...
使用 setInterval 动画
要使用 JavaScript 动画一个向右移动 400 像素的元素,基本操作是每隔一段时间移动 10 像素。
基于此逻辑的 HTML5 游戏通常以 ~60fps[2] 运行,但如果动画过于复杂或在低规格设备(例如移动电话)上运行,并且处理一帧需要超过 16 毫秒,那么游戏将以更低的帧速率运行:当处理一帧需要 33 毫秒时,游戏以 30fps 运行,游戏元素移动速度是应有速度的两倍。动画看起来仍然很流畅,但游戏体验会发生变化。
以恒定速度动画
要以恒定速度动画,我们需要计算自上一帧以来的时间差,并将元素按比例移动。
使用 requestAnimationFrame 动画
由于间隔参数在复杂动画中无关紧要,因为无法保证它会得到满足,因此设计了一个新的 API:requestAnimationFrame。这只是告诉浏览器“在屏幕上绘制下一帧之前,执行此游戏逻辑/动画处理”的一种方式。浏览器负责选择执行代码的最佳时机,从而更有效地使用资源[3]。
以下是使用 requestAnimationFrame 进行动画的写法。
注意:以下代码片段不包含在当前浏览器中运行所需的特性检测和解决方法。如果您想尝试它们,您应该尝试使用 可用的 animLoop.js。
处理非活动选项卡
requestAnimationFrame 是针对另一个优势而构建的:让浏览器选择最佳帧间隔允许在非活动选项卡中拥有较长的间隔。用户可以玩一个 CPU 密集型游戏,然后打开一个新选项卡或最小化窗口,游戏将暂停[4],将资源留给其他任务使用。
注意:这种行为对资源和电池使用率的潜在影响是如此积极,以至于浏览器供应商决定将其用于 setTimeout 和 setInterval[5]。
这种行为还意味着,当切换回包含动画的选项卡时,计算出的时间差可能非常高。这将导致动画看起来跳跃或产生“虫洞”[6],如这里所示。
可以通过将时间差限制为最大值或当时间差过高时不渲染帧来修复虫洞。
JSFiddle 演示.
动画队列问题
像 jQuery 这样的库会将元素上的动画排队,以便按顺序执行它们。此队列通常仅用于有意的连续动画。
但是,如果动画由计时器触发,则队列可能会在非活动选项卡中无限增长,因为暂停的动画会堆积在队列中。当切换回受影响的选项卡时,用户会看到大量动画连续播放,而实际上应该在正常间隔内播放一个动画。
这个问题在某些自动播放幻灯片(例如 mb.gallery)中可见。为了解决这个问题,开发者可以在触发新动画之前清空动画队列[7]。
JSFiddle 演示.
结论
setTimeout 和 setInterval 以及 requestAnimationFrame 的延迟是不可预测的,并且在非活动选项卡中要长得多。在编写动画逻辑、fps 计数器、时间倒计时以及时间测量至关重要的地方,都应该考虑这些因素。
[1] DOM 现在可以使用 CSS3 过渡 和 CSS3 动画 进行动画。
[2] 每 16 毫秒 1 帧是每秒 62.5 帧。
[3] 请参阅 msdn 上的 此事实的说明。
[4] requestAnimationFrame 在非活动选项卡中的行为仍在 w3c 中进行开发,并且可能在其他浏览器中有所不同。
[5] 请参阅 相关的 Firefox 错误 和 相关的 chromium 错误。
[6] 此术语首先由 Seth Ladd 在其“HTML5 游戏开发简介”演讲中提出。
[7] 请参阅您的 js 库的文档,例如 jQuery 的 effects 和 stop()。
关于 louisremi
开发者关系团队,长期 jQuery 贡献者和开放网络爱好者。 @louis_remi
12 条评论