setinterval(updatetime, 1000) 不等于走动,因其执行不精准、后台节流、易跳秒且存在 xss 风险;应改用 requestanimationframe + 秒级校准,配合 textcontent 和 css 微动画实现真走动。

直接用 setInterval 每秒更新一次 DOM 是最简单可行的方式,但要注意它不等于“走动”——秒针跳变、卡顿、页面隐藏后还在跑,都是常见问题。真正平滑的走动效果需要更精细的时间判断和渲染时机控制。
为什么 setInterval(updateTime, 1000) 不等于走动
setInterval 只保证「大约每秒执行一次」,不保证执行时刻精准。如果主线程正忙(比如在跑大循环或解析大量 JSON),这次回调就会被推迟,导致时间显示滞后甚至跳秒。用户看到的就是“咔哒咔哒”跳,不是连续走动。
- 浏览器标签页切到后台时,
setInterval可能被节流(如降频到最低 1s 甚至暂停),恢复后直接跳过中间秒数 - 每次更新都写
innerHTML会触发 HTML 解析,有 XSS 风险且比textContent慢 - 没做毫秒级对齐,即使你用
new Date()获取时间,也可能在 1000ms 到达前就渲染了旧值
用 requestAnimationFrame + 秒级校准实现真走动
核心思路是:用 requestAnimationFrame 驱动渲染节奏,但只在真实秒数变化时才更新 DOM —— 这样既利用了浏览器重绘的稳定性,又避免了无效刷新,视觉上更连贯。
关键点:
- 记录上一次渲染的秒数(
lastSecond),每次进入动画帧都取new Date().getSeconds()对比 - 仅当秒数变化时才调用格式化和写入逻辑,其余帧什么也不做
- 首次启动立即执行一次,避免首屏空白
- 用
textContent替代innerHTML,防注入且更快
function startClock() {
const el = document.getElementById('clock');
let lastSecond = -1;
function tick() {
const now = new Date();
const sec = now.getSeconds();
if (sec !== lastSecond) {
const h = String(now.getHours()).padStart(2, '0');
const m = String(now.getMinutes()).padStart(2, '0');
const s = String(sec).padStart(2, '0');
el.textContent = `${h}:${m}:${s}`;
lastSecond = sec;
}
requestAnimationFrame(tick);
}
tick(); // 立即执行一次
}
如何让“走动”看起来更自然(非必须但很有效)
纯数字刷新还是有点机械。加一点 CSS 动画过渡,能让每次秒变都有轻微“入场感”,用户感知更柔和。
- 给时间容器加
transition: opacity 0.2s ease,配合每次更新前先设opacity: 0.7,再设回1 - 或者用
transform: scale(0.98)配合transition做微小缩放反馈 - 注意:不要对整个
<div> 做位移动画,否则会影响可访问性(屏幕阅读器可能误读) <li>避免用 <code>@keyframes循环旋转,那只是装饰,和时间走动逻辑无关 - IE11 不支持
padStart,得用(n 回退 - 某些安卓 WebView(尤其旧版)中
requestAnimationFrame在页面不可见时仍会触发,需监听visibilitychange手动暂停 -
new Date()返回的是本地时间,如果用户手动改了系统时间,时钟会突变——无法靠 JS 修复,只能接受 - 服务端渲染(SSR)场景下,首次 HTML 中的时间是静态的,JS 加载后才开始走动,存在闪动,需用
data-属性预埋初始时间并比对
容易被忽略的兼容性和边界问题
多数人只测 Chrome,但真实环境里这些细节会出问题:
真正的走动效果不在于“每毫秒都动”,而在于“每次动都准、稳、不突兀”。重点不在刷新频率,而在刷新时机和 DOM 更新策略是否贴合浏览器渲染机制。











