JavaScript 倒計時踩坑集錦

前陣子,項目中加了個倒計時的需求,接手的時候前端

複製粘貼幹

啪啪啪三聲,搞定,送測web

let countdown = 100000; // ms 服務器返回的倒計時剩餘時間

function startCountdown() {
    setTimeout({
        countdown -= 1000;
        if (/* some */) {
            // do some
            startCountdown();
        }
    }, 1000);
}
複製代碼

某個彩筆開發:這波有bug我吃shi。面試

而後測試小姐姐反手就給了我幾個 bug chrome

哭

  • bug1: 你這東西不許啊,我看着幾分鐘,有好幾秒的延遲
  • bug2: 你這東西有問題啊,我縮小瀏覽器,等一會再打開,延遲了幾分鐘

某個彩筆開發:不可能,我再自測一下,打你臉,我測的時候明明沒問題。瀏覽器

五分鐘後:額,額,額~emmmmmmmmm....服務器

好嘛,我改。微信

緣由

瀏覽器中的定時器任務是有偏差的,也就是咱們常說的 setTimeout 爲何不許的問題,這裏涉及到 js 單線程以及運行機制,感興趣的能夠去了解一下,不少文章都有介紹。測試

在個人代碼中,形成 bug 的緣由:優化

  • 沒考慮偏差的疊加,也就是沒有處理偏差
  • 沒考慮瀏覽器的"休眠"

處理

1. 沒考慮偏差的疊加,也就是沒有處理偏差

先看一下優化後的代碼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);
複製代碼

這裏咱們經過對下一次任務的調用時間作了調整,前面延遲了多少毫秒,那麼我下一個任務執行就加快多少毫秒,這就是處理倒計時偏差的基本思路。

2. 沒考慮瀏覽器的"休眠"

On most browsers inactive tabs have low priority execution and this can affect JavaScript timers.

用我蹩腳的英語翻譯一下:在大多數瀏覽器中,待用的 tab 頁優先級較低,這會對 JavaScript 的定時器形成影響。

上面的大多數瀏覽器不包括 ie,在 ie 下沒有這個問題(ie真好用)。

蹩腳 + 歸納:爲了節能,對於運行在後臺的 tab 頁,定時器的延遲毫秒數被咱們設置爲 >= 1000ms。

相關連接:

針對第二點

複製上面的代碼,在瀏覽器調試器裏面試試,切換 tab 頁後,明顯的能夠看出延遲了。

關於這一點處理,其實更多的跟業務相關,不一樣的業務可能有不同的處理方式。

由個人需求引伸出一個例子:

  • 網頁實現一個 5 天的倒計時功能
  • 倒計時的剩餘數經過請求獲取,初始爲432000(s),也就是5天,而且服務器端也會進行一個倒計時

我猜,目前大部分的倒計時功能都是這樣實現的,前端其實只是負責一個倒計時UI的顯示,能讓用戶感知到有這麼一回事,真正倒計時的仍是放在了服務器端。

前面看到了切換 tab,或者網頁最小化時,有延遲,那麼咱們只要監聽用戶何時回到頁面,這個時候再去請求服務器端最新的剩餘時間,從新開始倒計時,修正形成的延遲。

目前有兩種方案監聽:

對於window.focus + window.blur,即便網頁仍呈如今用戶面前,也會觸發此事件,好比從瀏覽器切到微信聊天(此時網頁依然可見),可是仍會觸發 window.blur 事件。

所以我利用 visibilitychange 事件來處理切換 tab 頁以及瀏覽器最小化時的倒計時偏差修正。

// 處理頁面可見屬性的改變
document.addEventListener('visibilityChange', () => {
    if (!document.hidden) {
      // get newest downtime
    }
});
複製代碼

總結

在查資料的同時,好像還有幾種方案也能夠實現倒計時。

可是 requestAnimationFrame 好像也不能解決第二種狀況:

Web Worker 過兩天玩一下,還沒搞過呢。

第一次寫文章,寫得很差請見諒,也請指出。有更好的方法也能夠分享一下,感恩~!

相關文章
相關標籤/搜索