前陣子,項目中加了個倒計時的需求,接手的時候前端
啪啪啪三聲,搞定,送測web
let countdown = 100000; // ms 服務器返回的倒計時剩餘時間
function startCountdown() {
setTimeout({
countdown -= 1000;
if (/* some */) {
// do some
startCountdown();
}
}, 1000);
}
複製代碼
某個彩筆開發:這波有bug我吃shi。面試
而後測試小姐姐反手就給了我幾個 bug chrome
某個彩筆開發:不可能,我再自測一下,打你臉,我測的時候明明沒問題。瀏覽器
五分鐘後:額,額,額~emmmmmmmmm....服務器
好嘛,我改。微信
瀏覽器中的定時器任務是有偏差的,也就是咱們常說的 setTimeout 爲何不許的問題,這裏涉及到 js 單線程以及運行機制,感興趣的能夠去了解一下,不少文章都有介紹。測試
在個人代碼中,形成 bug 的緣由:優化
先看一下優化後的代碼ui
let countdown = 100000; // ms 服務器返回的倒計時剩餘時間
let countIndex = 1; // 倒計時任務執行次數
const timeout = 1000; // 觸發倒計時任務的時間間隙
const startTime = new Date().getTime();
startCountdown(timeout);
function startCountdown(interval) {
setTimeout(() => {
const endTime = new Date().getTime();
// 誤差值
const deviation = endTime - (startTime + countIndex * timeout);
console.log(`${countIndex}: 誤差${deviation}ms`);
countIndex++;
// 下一次倒計時
startCountdown(timeout - deviation);
}, interval);
}
複製代碼
幾乎沒有什麼邏輯的執行快,就已經有每秒平均 5ms 的延遲,那麼 10 分鐘的延遲將會累加到 3000ms。
倒計時偏差是不能避免的,可是咱們能儘量的減少這個偏差。
// 下次倒計時任務執行的等待時間 = 1s - 偏差
startCountdown(timeout - deviation);
複製代碼
這裏咱們經過對下一次任務的調用時間作了調整,前面延遲了多少毫秒,那麼我下一個任務執行就加快多少毫秒,這就是處理倒計時偏差的基本思路。
On most browsers inactive tabs have low priority execution and this can affect JavaScript timers.
用我蹩腳的英語翻譯一下:在大多數瀏覽器中,待用的 tab 頁優先級較低,這會對 JavaScript 的定時器形成影響。
上面的大多數瀏覽器不包括 ie,在 ie 下沒有這個問題(ie真好用)。
蹩腳 + 歸納:爲了節能,對於運行在後臺的 tab 頁,定時器的延遲毫秒數被咱們設置爲 >= 1000ms。
相關連接:
複製上面的代碼,在瀏覽器調試器裏面試試,切換 tab 頁後,明顯的能夠看出延遲了。
關於這一點處理,其實更多的跟業務相關,不一樣的業務可能有不同的處理方式。
由個人需求引伸出一個例子:
我猜,目前大部分的倒計時功能都是這樣實現的,前端其實只是負責一個倒計時UI的顯示,能讓用戶感知到有這麼一回事,真正倒計時的仍是放在了服務器端。
前面看到了切換 tab,或者網頁最小化時,有延遲,那麼咱們只要監聽用戶何時回到頁面,這個時候再去請求服務器端最新的剩餘時間,從新開始倒計時,修正形成的延遲。
目前有兩種方案監聽:
對於window.focus + window.blur,即便網頁仍呈如今用戶面前,也會觸發此事件,好比從瀏覽器切到微信聊天(此時網頁依然可見),可是仍會觸發 window.blur 事件。
所以我利用 visibilitychange 事件來處理切換 tab 頁以及瀏覽器最小化時的倒計時偏差修正。
// 處理頁面可見屬性的改變
document.addEventListener('visibilityChange', () => {
if (!document.hidden) {
// get newest downtime
}
});
複製代碼
在查資料的同時,好像還有幾種方案也能夠實現倒計時。
可是 requestAnimationFrame 好像也不能解決第二種狀況:
Web Worker 過兩天玩一下,還沒搞過呢。
第一次寫文章,寫得很差請見諒,也請指出。有更好的方法也能夠分享一下,感恩~!