移动浏览器最小化时Javascript结合Web Worker的setInterval倒计时变慢如何解决?
问题根因
移动端操作系统和浏览器为了降低后台功耗,会对非活跃标签页、最小化浏览器里的定时器执行做降频限制,即便把定时器放在Web Worker里也逃不过系统层面的调度限制,你现有代码中采用每次定时器触发就distance--的累加计数逻辑,只要定时器少触发一次就会出现1秒的偏差,自然会出现倒计时变慢的问题。
修复方案
1. 核心逻辑调整
放弃依赖定时器执行频率的累加计数逻辑,改用倒计时结束的绝对时间戳差值计算剩余时长,不管定时器什么时候触发,都按当前时间和结束时间的差值计算剩余时长,完全不受定时器降频影响。
2. 结合现有可见性事件做修正
你已经用到了visibilitychange事件,刚好可以在页面从后台切回前台时,立刻用最新的时间差刷新倒计时,抵消后台期间的定时器延迟影响。
3. 修正后的代码示例
script.js
// 提前定义倒计时结束的绝对时间戳,存在主线程不要放在Worker内 let endTimestamp = Date.now() + 你的总倒计时毫秒数; // 替换为实际的结束时间戳 let hidden, visibilityChange, w; if (typeof document.hidden !== "undefined") { hidden = "hidden"; visibilityChange = "visibilitychange"; } else if (typeof document.msHidden !== "undefined") { hidden = "msHidden"; visibilityChange = "msvisibilitychange"; } else if (typeof document.webkitHidden !== "undefined") { hidden = "webkitHidden"; visibilityChange = "webkitvisibilitychange"; } document.addEventListener(visibilityChange, function (event) { if( document[hidden] === false ){ // 页面切回前台,给Worker发送最新结束时间戳强制刷新 if(w) { w.postMessage({ 'endTimestamp': endTimestamp }); } } }); function startTimer(endTime) { endTimestamp = endTime; if (typeof(Worker)!=="undefined"){ if(w == null){ w = new Worker("../js/timer-worker.js"); w.postMessage({ 'endTimestamp': endTimestamp }); } w.onmessage = function (event) { console.log(event.data.time_left_str); // 倒计时结束销毁Worker if(event.data.total_seconds <= 0) { w.terminate(); w = null; } }; } else { console.log('Sorry, your browser does not support Web Workers ...'); } }
timer-worker.js
let endTimestamp = null; let timer = null; function pad(n) { return (n < 10 ? "0" + n : n); } function myTimer() { if(!endTimestamp) return; const now = Date.now(); // 直接用结束时间减当前时间计算剩余秒数,不受定时器执行频率影响 let distance = Math.max(0, Math.floor((endTimestamp - now) / 1000)); const hours = Math.floor((distance % (60 * 60 * 24)) / (60 * 60)); const minutes = Math.floor((distance % (60 * 60)) / 60); const seconds = Math.floor(distance % 60); let time_left_str = ''; if( hours > 0 ){ time_left_str = pad(hours) + " hrs " + pad(minutes) + " mins " + pad(seconds) + " secs "; } else if( minutes > 0 ){ time_left_str = pad(minutes) + " mins " + pad(seconds) + " secs "; } else { time_left_str = pad(seconds) + " secs "; } postMessage({ 'time_left_str': time_left_str, 'total_seconds': distance, 'distance_changed': distance }); // 倒计时结束自动清除定时器 if(distance <= 0) { clearInterval(timer); } } self.addEventListener('message', function(event) { if(event.data.endTimestamp) { endTimestamp = event.data.endTimestamp; // 收到新的结束时间立刻刷新一次倒计时 myTimer(); } }, false); // 定时器仅负责触发刷新,不再承担计数功能 timer = setInterval(myTimer, 1000);
4. 极端场景优化
如果需要支持长时间后台倒计时,可额外把结束时间戳存储在localStorage中,就算页面被系统后台杀掉重启,也能直接读取存储的结束时间戳恢复正确的倒计时进度。
内容的提问来源于stack exchange,提问作者Sandip Nandy
相关产品推荐
相关产品推荐

