倒計時為啥不準(zhǔn)
先說原因:JavaScript是單線程,setTimeout (主線程空閑時才會去執(zhí)行任務(wù))的回調(diào)函數(shù)會被放入事件隊列,既然要排隊,就可能被前面的任務(wù)阻塞導(dǎo)致延遲 。且任務(wù)本身從call stack(執(zhí)行棧)中拿出來執(zhí)行也要耗時。所以有1000變1002也合理。就算setTimeout的第二個參數(shù)設(shè)為0,也會有至少有4ms的延遲。
let total = 9 // 倒計時9s
let preStamp = Date.now();
let nowStamp = preStamp;
const countDown = ()=>{
if(total > 0){
preStamp = nowStamp
nowStamp = Date.now();
total--
console.log('最后時間:',new Date().toLocaleString(),'總共耗時:', nowStamp - preStamp)
setTimeout(countDown ,1000)
}
}
countDown()
正常情況下,瀏覽器tab頁是激活或者顯示狀態(tài)下:

image.png
有幾毫秒的誤差也正常,畢竟setTimeout是先把任務(wù)放到任務(wù)隊列里,等主線程空閑時才會去執(zhí)行。
但是有一種情況,就是tab頁或者頁面不顯示或者隱藏時,倒計時函數(shù)的誤差就會非常大:

image.png
原因:當(dāng)頁面處于后臺時,瀏覽器會降低定時器的執(zhí)行頻率以節(jié)省資源,導(dǎo)致 setTimeout 的延遲增加。切回來后又正常了
解決辦法
- 監(jiān)聽 visibilitychange 事件,在切回tab時修正(不推薦)
let timer:any = null;
let total = 20 // 倒計時9s
let preStamp = Date.now();
let nowStamp = preStamp;
useEffect(() => {
const handleVisibilityChange = () => {
console.log('Page is visible:', document.visibilityState);
if(document.visibilityState === 'visible'){
preStamp = Date.now();
nowStamp = preStamp;
countDown()
}else{
timer && clearTimeout(timer)
}
};
// 添加事件監(jiān)聽器
document.addEventListener('visibilitychange', handleVisibilityChange);
// 清理函數(shù):移除事件監(jiān)聽器
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
})
const countDown = ()=>{
if(total > 0){
preStamp = nowStamp
nowStamp = Date.now();
total--
nowStamp - preStamp && console.log('最后時間:',new Date().toLocaleString(),'總共耗時:', nowStamp - preStamp)
timer = setTimeout(countDown ,1000)
}
}
countDown()
當(dāng)前代碼是,tab隱藏的時候停掉倒計時,可以保證頁面顯示的時候倒計時還是每一秒執(zhí)行一次(這種情況只能用來控制動畫,如果是有特殊操作,就不能用這個方法了)。有的博主可能是在tab隱藏時調(diào)整倒計時的頻次,就是setTimeout的第二個參數(shù),但是我嘗試了,不停掉計時器,倒計時會一直跑,而且,隱藏的tab頁面時間越久,誤差會越大,沒有任何意思!

image.png
- web worker(推薦)
用web worker,單獨的線程去計時,不會受切tab影響
//worker.ts
let total = 9;
let startTime = Date.now();
let preStamp = Date.now();
let nowStamp = preStamp;
const countDown = () => {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
const remaining = total - elapsed;
if (remaining > 0) {
preStamp = nowStamp
nowStamp = Date.now();
postMessage({cutN: remaining, time: nowStamp - preStamp});
setTimeout(countDown, 1000);
} else {
postMessage(0);
}
};
countDown();
//index.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url));
worker.onmessage = (e) => {
console.log('剩余時間:', e.data.cutN, '當(dāng)前時間:', new Date().toLocaleString(), e.data.time);
};
tab頁面隱藏后,倒計時正常執(zhí)行,準(zhǔn)確度也很好;

image.png
//文中有不對或者不嚴謹?shù)牡胤街x謝這正,小的看見會立刻糾正改過,重新做人?。?!