前端網頁倒計時是非常常見的應用,我們在各大購物網站的秒殺活動中總是能見到它的身影。但是在實際情況中,我們常常會發現當網頁不刷新、讓倒計時程序持續運行時,顯示時間相比實際時間會越來越慢,相信大家也有在秒殺時間即將到來時不停刷新頁面的經歷。原因自然也不難理解:倒計時通常使用定時器(setTimeout
或者 setInterval
)實現,而 JavaScript 的單線程特性使得主線程執行棧中出現阻塞時,任務隊列中的異步任務並不能及時執行,因此瀏覽器並不能保證在定時器設置的時間結束後代碼總是被準時執行,這就造成了倒計時的偏差。
一般的解決方法是前端定時向服務器發送請求獲取最新的時間差來校準倒計時時間,主動(程序裏設置定時請求)或被動的(F5 已被用戶按壞)區別而已。這個方法簡單但也有點粗暴,下面提供一種方法,能夠一定程度上不依賴服務端實現倒計時的糾偏。代碼非原創,時間久遠忘了出處,在此記錄一下學習過程以免遺忘。如有侵權請聯繫我。
首先我們需要模擬主線程阻塞的環境,同時又不能讓主線程一直阻塞:
setInterval(function(){
let j = 0
while(j++ < 100000000)
}, 0)
然後是主要的代碼:
const interval = 1000
let ms = 50000, // 從服務器和活動開始時間計算出的時間差,這裏測試用 50000 ms
let count = 0
const startTime = new Date().getTime()
let timeCounter
if( ms >= 0) {
timeCounter = setTimeout(countDownStart, interval)
}
function countDownStart () {
count++
const offset = new Date().getTime() - (startTime + count * interval) // A
const nextTime = interval - offset
if (nextTime < 0) {
nextTime = 0
}
ms -= interval
console.log(`誤差:${offset} ms,下一次執行:${nextTime} ms 後,離活動開始還有:${ms} ms`)
if (ms < 0) {
clearTimeout(timeCounter)
} else {
timeCounter = setTimeout(countDownStart, nextTime)
}
}
代碼的基本原理並不複雜:通過遞歸調用 setTimeout
進行倒計時操作的執行。而每次執行函數時會維護一個 count 變量,用以記錄已經執行過的倒計時次數,使用代碼 A 處的公式可計算出當前執行倒計時的時間與實際應執行時間的偏差,進而可以計算出下次執行倒計時的時間。
本文首發於我的博客(點此查看),歡迎關注。