活動倒計時實現方案

個人文章也有插圖

聊一下活動倒計時的一些實踐方案與思考。方案整體來講分爲兩種:segmentfault

方案一:依賴本地時間

固然不能簡單粗暴的直接獲取本地時間來倒計時,結果多是每一個用戶的倒計時時間千差萬別,並且用戶能夠惡意更改本地時間去繞過倒計時操做。瀏覽器

方案:接口獲取到服務前當前時間之後,與本地時間作一個差值計算。循環中使用本地時間加時間差得出當前時間,來計算倒計時時間,並在必定頻率內更新服務器、本地時間差值。服務器

// 接口獲取服務器當前時間,並計算與本地時間的時間差
const timeDiff = serverTime - new Date().getTime()
setTimeout(() => {
    // 當前時間 = 本地時間 + 時間差 
    const currTime = new Date().getTime() + timeDiff
    // 倒計時時間 = 截止時間 - 當前時間
    const countDown = endTime - currTime
    // 顯示倒計時信息
    setCountDownInfo(countDown)
}, 1000)
複製代碼

優勢:

  • js倒計時有延遲。若是單純使用jssetTimeout倒計時,代碼運行時間越長誤差越大,本地時間的準確性就很能夠實時糾正js倒計時的誤差。
  • pc端瀏覽器進入後臺運行,js計時器會變慢;移動端應用進入後臺運行,js計時器會直接中止,致使倒計時嚴重誤差。而依賴本地時間,就能夠糾正這些誤差。

缺點:

用戶也是能夠修改本地時間繞過計時器的。須要必定的頻率,獲取服務器時間更新時間差值,防止用戶修改本地時間ui

方案二:js倒計時

方案:獲取到服務器時間之後,js用setTimeout作倒計時,不依賴本地時間。spa

// 接口獲取服務器當前時間
let currTime = serverTime
setTimeout(() => {
    // 當前時間 = 服務器時間 循環累加
    currTime = currTime + 1000
    // 倒計時時間 = 截止時間 - 當前時間
    const countDown = endTime - currTime
    // 顯示倒計時信息
    setCountDownInfo(countDown)
}, 1000)
複製代碼
  • 優勢:解決了依賴本地時間的弊端線程

  • 缺點:前邊提過,js定時器有誤差;頁面後臺運行,定時器會變慢。須要必定的頻率,獲取服務器當前時間矯正這些誤差code

思考一:校準時間的頻率

不管上述哪一種方案,不管矯正js定時器誤差仍是更新時間差值防止修改本地時間頻率的設定都很重要,能夠根據場景區分cdn

1. 倒計時的重要性:

  • 直接控制秒殺或購買按鈕的倒計時,準確性要求最高,因此須要頻率相對高;
  • 其次就是浮標、活動入口按鈕、落地頁等提示類倒計時,不涉及主要業務邏輯,準確性要求低,能夠頻率設定相對低;

2. 距離倒計時結束的時長

  • 距離還很長時,更新頻率高,不必並且很浪費服務器資源。
  • 倒計時立刻結束時,更新頻率很低,精度就會不好。

因此一般倒計時越接近結束時,準確性要求越高。能夠根據倒計時時長,來動態設定更新頻率,倒計時距離結束越近,更新的頻率越高,而且設定頻率最高閾值;反之同理。server

好比倒計時爲5分鐘時,30s更新一次:(5 * 60 * 1000) / 30 = 當前時間差(ms) / 當前頻率(s),就能夠獲取動態的更新頻率blog

3. 固然頻率具體的數值,還要根據接口穩定性和用戶數量決定

思考二: 使用setTimeout仍是setInterval

試想:

  • 若是setInterval定時器間隔時間老是小於操做執行時間時,事件隊列中就會排起隊 :本次循環事件未執行完,下次循環的事件已經添加進隊列中
  • 並且 當使用 setInterval時,僅當沒有該定時器的任何其餘代碼實例時,纔將定時器代碼添加到隊列中

因此:

  • 丟幀: 那麼下次循環結束時,由於隊列中已經有它的一個實例,就不會向隊列中添加事件了,因此此次事件執行就會丟失。
  • 並且當前的事件執行完畢後,就會立刻執行隊列中已經添加的事件,這兩次事件執行的時間間隔就會變小甚至無間隔

總結:

必定時間間隔重複作某些操做時,setInterval可能會出現丟幀操做之間無時間間隔的狀況。因此儘可能使用setTimeout,它能夠保證事件在相同時間間隔內執行,而且不丟失事件

參考這篇文章

思考三: 爲何setTimeout時間間隔會有誤差

setTimeout只是負責準時的把事件添加進事件隊列中,可是若是js線程在忙於執行其餘任務 (處理用戶交互、js代碼執行等),那麼這個事件就須要等待線程空閒了才能被執行

若是setTimeout遞歸實現倒計時,上述等待js線程產生的延遲就會在不停的循環中被疊加累計,延遲誤差就會愈來愈大,計時就會愈來愈不許。

相關文章
相關標籤/搜索