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 避坑指南

嗯,这里我整理了几个常见的坑,都是我在项目中踩过的:

  1. 忘记清除定时器:组件卸载或页面跳转时,一定要 clearTimeout/clearInterval。否则回调会继续执行,导致内存泄漏甚至报错。
  2. 定时器嵌套导致时间不准:setTimeout 递归调用时,每次回调的执行时间会累加。如果回调本身耗时 50ms,间隔设为 100ms,那实际间隔是 150ms。
  3. 页面不可见时继续执行:用户切到其他标签页,定时器依然在跑。这不仅是性能浪费,还可能导致动画状态错乱。
  4. 用 setInterval 做动画:这个我前面提过,能不用就别用。setInterval 的累积误差和回调堆积问题,在动画场景下特别致命。

一句话总结:定时器动画就像一把瑞士军刀——功能多、上手快,但别指望它干精细活。做复杂动画时,还是交给 requestAnimationFrame 吧。

下一章我会详细讲 requestAnimationFrame,到时候你会看到,同样是基于时间的动画,rAF 比定时器优雅了不止一个档次。但在此之前,先把定时器玩明白,这对你理解动画的底层原理很有帮助。