游客发表
京东秒杀页的倒计时,不是秒杀码简单的 setInterval 每秒减一。你要是倒计真这么写,用户切个标签页回来,时代时间就错乱了。京东为什么?秒杀码因为浏览器对后台标签的定时器会降频,甚至暂停。倒计你以为是时代1秒走一次,实际可能过了10秒才补一次回调。京东所以核心只有一句话:倒计时的秒杀码唯一可靠基准是服务器时间差,不是倒计本地累计。
我拆过京东的时代秒杀模块,它的京东做法是——页面加载时拿一次服务器时间 serverTime,再记录本地当前时间 localTime。秒杀码之后每一次更新,倒计都读取此刻的本地时间,用 serverTime + (Date.now() - localTime) 算出“当前真实时间”。然后目标结束时间减去这个值,就是剩余毫秒。这样哪怕定时器卡了30秒,只要恢复执行,一算差值,时间还是准的。
这段逻辑,代码量不大,但细节在格式化和边界。
先看核心函数。假设后端给你一个 endTime(时间戳,毫秒级)。页面初始化时,通过接口拿到 serverTime。然后启动一个 setInterval,间隔250毫秒——别用1000,因为网络延迟或主线程阻塞会让人眼看到“跳秒”。250毫秒的刷新频率,既平滑又不至于太耗性能。
// 伪代码,实际需封装 let serverTime = 0; // 从接口获取 let localTime = Date.now(); let endTime = 0; // 从接口获取 function getCurrentTime() { return serverTime + (Date.now() - localTime); } function updateCountdown() { const remain = endTime - getCurrentTime(); if (remain <= 0) { // 结束处理 clearInterval(timer); document.getElementById('countdown').textContent = '已结束'; return; } const hours = Math.floor(remain / 3600000); const minutes = Math.floor((remain % 3600000) / 60000); const seconds = Math.floor((remain % 60000) / 1000); // 补零操作 document.getElementById('countdown').textContent = `${ String(hours).padStart(2,'0')}:${ String(minutes).padStart(2,'0')}:${ String(seconds).padStart(2,'0')}`; } const timer = setInterval(updateCountdown, 250); 但有个坑。如果你直接拿 Date.now() 做基准,而用户改了系统时间,倒计时会瞬间跳变。京东的秒杀倒计时页面,其实不关心这种极端情况,因为秒杀本身就是短时活动,用户改时间影响自己,无伤大雅。但如果你做的是拍卖或抢购,最好用 performance.timeOrigin 加 performance.now(),这个不受系统时间影响,只跟页面启动时间有关。不过京东没用这个,因为服务器时间差已经够用了。
再说一个细节。秒杀倒计时通常显示“时:分:秒”,但京东有时候会显示“天:时:分:秒”,比如距离开始还剩1天。判断一下 remain 是否大于86400000,然后决定显示格式。我见过不少开发者直接写死三位数,结果活动超过24小时就显示“25:00:00”,用户看着别扭。
另外,页面要处理网络请求失败的情况。如果初始化时拿不到 serverTime,不能瞎倒计时。京东的做法是重试三次,期间显示“加载中”。如果最终失败,直接用本地时间跑,但会标注“时间可能有偏差”。实际上,秒杀这种场景,网络失败的概率极低,但代码里还是得兜底。
还有个小技巧。倒计时结束后,不要只是改文字,要清理定时器。否则用户停留在页面,每秒都在跑一个空函数,白白耗电。京东还会在结束后触发一个回调,比如刷新页面或显示“已抢光”。这个回调要跟业务解耦,别写在倒计时函数里。
写秒杀倒计时,最烦的是测试。你不能真的等一小时看效果。所以代码里要留一个调试入口——比如在控制台执行 window.__setEndTime(Date.now() + 5000),强制改变结束时间。京东的debug版本就有这个,只是压缩后去掉了。
最后补充一个容易被忽略的点:页面可见性变化。当用户切到其他标签页再回来,定时器可能被浏览器挂起。但因为我们用的是服务器时间差,所以回来的一瞬间,更新函数会立刻计算出正确剩余时间。不需要额外监听 visibilitychange。但如果你做了动画或者闪烁效果,最好在可见时重新渲染一次,避免视觉残留。
这套逻辑,说透了不值钱,但没踩过坑的人,第一次写必然出错。我自己就见过同事把 serverTime 和 localTime 搞反,结果倒计时越来越快,用户截图投诉。所以,记住核心:时间差只算一次,之后全靠本地时钟加差值推导。 别每秒都去请求服务器,那不是秒杀,是自杀。
代码本身不复杂,复杂的是边界情况:跨天、格式补零、网络抖动、定时器漂移。把这些都处理干净,你的倒计时就能像京东一样稳。至于UI怎么设计,那是另一个话题了。
友情链接