用 setInterval 即可,设为 1000ms 间隔;时间差用服务端时间戳计算,拆解为天、时、分、秒;更新文本用 innerText;活动结束立即 clearInterval 并清理。

倒计时用 setInterval 还是 requestAnimationFrame?
用 setInterval 就够了,别被“更精准”忽悠。它每秒触发一次,对“天/小时/分钟”级倒计时完全够用;requestAnimationFrame 适合动画帧同步,但会受页面失焦、节流影响,反而导致跳秒或卡住。真实场景中,用户根本看不出 16ms 和 1000ms 的差别,但能明显感觉到“突然少了一小时”。
常见错误:用 setInterval 每 100ms 刷新——没必要,浪费 CPU,还可能因 JS 执行延迟造成累积误差。
- 推荐间隔设为
1000(1 秒),配合服务端下发的截止时间戳计算剩余 - 首次渲染立即执行一次,避免页面加载完才开始倒数
- 页面切到后台时,
setInterval仍会运行(Chrome 88+ 对隐藏页有节流,但倒计时逻辑不受影响)
怎么算“双倍经验剩余2天”里的天数和小时?
别用 Math.floor((endTime - now) / (1000 * 60 * 60 * 24)) 硬除——这算的是“整日”,但用户要的是“还剩 2 天 5 小时 12 分”,得拆解。核心是把毫秒差转成天、时、分、秒四个整数,且每个字段不进位(比如 48 小时不能显示成 2 天 0 小时,而要按实际剩余精确拆)。
容易踩的坑:直接用 getDay() 或 getDate() 算日期差,会受时区、夏令时、月份天数不一致干扰,结果错乱。
- 统一用时间戳相减:
const diffMs = endTime - Date.now() - 天数 =
Math.floor(diffMs / (1000 * 60 * 60 * 24)) - 剩余毫秒再取模,依次算出小时、分钟、秒:
Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) - 注意:如果
diffMs ,说明活动已结束,应停止定时器并隐藏或替换文案
HTML 中动态更新文本,用 innerText 还是 innerHTML?
用 innerText。你只是更新纯文本内容,比如“双倍经验剩余2天1小时”,没有 HTML 标签,也不需要保留空格缩进。innerHTML 会触发 HTML 解析,哪怕内容里没标签也多一道安全检查,白费性能;更危险的是,万一后端返回的数据含 <script></script> 或未转义的 ,就可能 XSS。
使用场景明确:只改文字,不改结构。DOM 节点早就存在,比如一个 ,每次只改它的文本。
- 获取元素:
const el = document.getElementById('countdown') - 更新文本:
el.innerText = `双倍经验剩余${days}天${hours}小时` - 避免反复查 DOM:把
el缓存到变量里,不要在定时器里每次都getElementById
活动结束时自动停掉倒计时,但页面没刷新怎么办?
必须手动清理。浏览器不会帮你停 setInterval,哪怕倒计时归零,定时器还在跑,持续消耗资源,还可能因后续 Date.now() 超过 endTime 导致负数显示(比如“剩余-1天-3小时”)。
关键不是“怎么停”,而是“什么时候停”——不能只靠 diffMs 判断一次,得在每次执行时都检查,并立刻清除。
- 定义定时器 ID:
let timerId = null - 在倒计时函数开头加判断:
if (diffMs - 别依赖
setTimeout去“预估”结束时间再清理——网络延迟、JS 阻塞都可能导致不准 - 补充容错:在页面卸载前也清理一次,
window.addEventListener('beforeunload', () => clearInterval(timerId))
最常被忽略的一点:时间戳必须来自服务端。前端用 Date.now() 是本地时间,用户调快系统时间,倒计时就变慢甚至倒流;活动开始/结束时间也必须由后端下发,不能写死在 JS 里。这个环节一错,整个倒计时就不可信。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











