使用 requestAnimationFrame 實現定時器,解決 setInterval 執行次數丟失問題
阿新 • • 發佈:2020-12-14
來看這樣一個場景:使用 setInterval 定時器倒計時,突然來了一個長達三秒的任務,定時器會有一次不準,兩次丟失回撥,導致少兩次計算時間。
// 在控制檯上輸入下面四行 var second = 0 setInterval(function() { console.log(`setInterval ${++second}`, new Date().getTime()) }, 1000) // 幾秒之後輸入下面程式碼 function sleep(ms) { const end = new Date().getTime() + ms console.log('sleep start') while (new Date().getTime() < end) {} console.log('sleep end') } sleep(3000)
如圖所示,少兩次回撥的執行。
requestAnimationFrame 實現定時器
requestAnimationFrame 傳入一個回撥函式,該回調函式會在瀏覽器下一次重繪之前執行,詳情檢視MDN文件 window.requestAnimationFrame
/** * 設定精度定時器 * @param {function} 回撥函式 * @param {number} 延遲時間 * @return {number} 定時器ID */ function setIntervalPrecision(callback, delay) { // 生成並記錄定時器ID let obj = window.interValPrecisionObj || (window.interValPrecisionObj = { num: 0 }) obj.num++ obj['n' + obj.num] = true var intervalId = obj.num // 開始時間 var startTime = +new Date() // 已執行次數 var count = 0 // 延遲時間 delay = delay || 0 ;(function loop() { // 定時器被清除,則終止 if (!obj['n' + intervalId]) return // 滿足條件執行回撥 if (+new Date() > startTime + delay * (count + 1)) { count++ callback(count) } requestAnimationFrame(loop) })() return intervalId } /** * 清除精度定時器 * @param {number} 定時器ID */ function clearIntervalPrecision(intervalId) { if (window.interValPrecisionObj) { delete window.interValPrecisionObj['n' + intervalId] } }
測試
// 在控制檯上輸入下面四行 setIntervalPrecision(function(val) { console.log(`setIntervalPrecision ${val}`, new Date().getTime()) }, 1000) // 幾秒之後輸入下面程式碼 function sleep(ms) { const end = new Date().getTime() + ms console.log('sleep start') while (new Date().getTime() < end) {} console.log('sleep end') } sleep(3000)
任務阻塞結束後,會瞬間執行阻塞期間需要執行次數的回撥,雖然倒計時頁面會卡三秒(js特性),但實際剩餘秒數不會出錯。