2、定时器动画:setTimeout与setInterval的对比、定时器动画的优缺点、实际应用场景
说到 JavaScript 动画,很多人第一反应就是 CSS 动画或者 requestAnimationFrame。但说实话,在早期前端开发中,定时器动画才是真正的主力军。即便在今天,某些场景下它依然有不可替代的价值。
我个人习惯把定时器动画叫做「土办法」,因为它够直接、够简单。你想想看,不就是每隔一段时间改一下元素的位置吗?但就是这个「土办法」,背后藏着不少坑。
2.1 setTimeout 与 setInterval 的核心差异
这两个 API 长得像双胞胎,但性格完全不同。我刚开始写动画时,经常搞混它们,结果页面要么卡死,要么动画跑飞。
| 特性 | setTimeout | setInterval |
|---|---|---|
| 执行方式 | 延迟后执行一次 | 每隔固定时间执行一次 |
| 递归调用 | 需要手动递归实现循环 | 自动循环 |
| 误差累积 | 无累积误差 | 有累积误差 |
| 控制灵活性 | 高(可动态调整间隔) | 低(固定间隔) |
| 内存泄漏风险 | 较低 | 较高(忘记 clear 的话) |
这里有个关键点:setInterval 不关心回调函数执行了多久。假如你的回调执行需要 200ms,而间隔设置的是 100ms,那回调还没跑完,下一次又开始了。这会导致回调堆积,页面越来越卡。
核心结论:做动画时,setTimeout 比 setInterval 更可控。我几乎不用 setInterval 做动画,除非是那种「丢了不管」的轮询任务。
2.2 定时器动画的优缺点
优点
- 兼容性极好:IE6 都能跑,你想想看这覆盖面有多广
- 实现简单:几行代码就能让一个方块动起来
- 控制力强:可以随时暂停、恢复、变速、反向
- 不依赖 CSS:适合那些需要动态计算位置的场景
缺点
- 精度不够:定时器的最小间隔是 4ms(HTML5 规范),而且受事件循环影响
- 掉帧问题:浏览器标签页切到后台时,定时器会被节流到 1000ms
- 性能开销:频繁操作 DOM 会导致重排重绘
- 代码维护性差:动画逻辑和业务逻辑混在一起,容易乱
我曾经在一个移动端项目中用 setInterval 做轮播图动画,结果用户切到其他 App 再回来,轮播图直接「瞬移」到了最后一张。原因就是后台节流导致动画状态错乱。从那以后,我养成了一个习惯:所有定时器动画都要监听页面可见性变化。
2.3 实际应用场景
虽然现在有了 requestAnimationFrame,但定时器动画在某些场景下依然是首选。我总结了几类典型场景:
场景一:低频率动画
比如一个数字滚动效果,每秒更新一次。这种频率下,定时器完全够用,用 rAF 反而有点杀鸡用牛刀。
// 数字滚动效果
let count = 0;
const target = 100;
function updateNumber() {
count++;
document.getElementById('counter').textContent = count;
if (count < target) {
setTimeout(updateNumber, 1000); // 每秒更新一次
}
}
updateNumber();
场景二:倒计时与进度条
倒计时对精度要求不高,差个几十毫秒用户根本感觉不到。而且倒计时通常需要暂停、恢复、重置,用 setTimeout 实现起来非常灵活。
// 倒计时动画
let remaining = 60; // 60秒
let timerId = null;
function startCountdown() {
function tick() {
if (remaining <= 0) {
console.log('倒计时结束');
return;
}
document.getElementById('timer').textContent = remaining--;
timerId = setTimeout(tick, 1000);
}
tick();
}
function pauseCountdown() {
clearTimeout(timerId);
}
function resumeCountdown() {
startCountdown();
}
场景三:与后端数据联动的动画
比如股票行情、实时比分、服务器推送的进度更新。这些场景下,动画的触发时机由后端数据决定,定时器只是用来做「过渡效果」。
我的经验:在这种场景下,我会把定时器当作「调度器」而不是「动画引擎」。定时器只负责检查数据是否有更新,实际的动画过渡交给 CSS transition 或 rAF 去处理。这样既保证了响应速度,又避免了频繁操作 DOM。
场景四:老旧系统兼容
说实话,我现在很少遇到需要兼容 IE8 的项目了。但如果你在做一些政府网站、银行系统或者教育类产品,定时器动画可能是唯一的选择。
2.4 避坑指南
嗯,这里我整理了几个常见的坑,都是我在项目中踩过的:
- 忘记清除定时器:组件卸载或页面跳转时,一定要 clearTimeout/clearInterval。否则回调会继续执行,导致内存泄漏甚至报错。
- 定时器嵌套导致时间不准:setTimeout 递归调用时,每次回调的执行时间会累加。如果回调本身耗时 50ms,间隔设为 100ms,那实际间隔是 150ms。
- 页面不可见时继续执行:用户切到其他标签页,定时器依然在跑。这不仅是性能浪费,还可能导致动画状态错乱。
- 用 setInterval 做动画:这个我前面提过,能不用就别用。setInterval 的累积误差和回调堆积问题,在动画场景下特别致命。
一句话总结:定时器动画就像一把瑞士军刀——功能多、上手快,但别指望它干精细活。做复杂动画时,还是交给 requestAnimationFrame 吧。
下一章我会详细讲 requestAnimationFrame,到时候你会看到,同样是基于时间的动画,rAF 比定时器优雅了不止一个档次。但在此之前,先把定时器玩明白,这对你理解动画的底层原理很有帮助。