JavaScript如何實(shí)現(xiàn)精準(zhǔn)倒計(jì)時(shí)
為什么使用setTimeout、setInterval做倒計(jì)時(shí)不準(zhǔn)?
1、主要原因與JavaScript的執(zhí)行機(jī)制有關(guān)。JavaScript是單線程的,這意味著它一次只能執(zhí)行一個(gè)任務(wù)。當(dāng)你設(shè)置一個(gè)計(jì)時(shí)器時(shí),計(jì)時(shí)器的回調(diào)函數(shù)會(huì)被放入任務(wù)隊(duì)列中,等待事件循環(huán)來處理。然而,如果在計(jì)時(shí)器到期時(shí),調(diào)用棧中有其他任務(wù)正在執(zhí)行,計(jì)時(shí)器的回調(diào)函數(shù)就會(huì)被延遲執(zhí)行。這種延遲會(huì)導(dǎo)致計(jì)時(shí)器不準(zhǔn)確。
2、此外,瀏覽器對(duì) setTimeout 和 setInterval 的最小時(shí)間間隔有一定的限制,通常在4毫秒或更高,這也會(huì)影響計(jì)時(shí)的精度。系統(tǒng)資源的限制,如CPU負(fù)載過高,也可能導(dǎo)致計(jì)時(shí)器的回調(diào)函數(shù)被延遲執(zhí)行。
知道了原因,問題就比較好解決了。
既然由于js是單線程機(jī)制,那我們就專門開一個(gè)線程來進(jìn)行倒計(jì)時(shí)不就行了,web worker 登場(chǎng)。
web worker 方案
import { ref } from 'vue'
?
let countDownWorker: Worker | null = null
export default function useCountDownWorker(defaultTime: number) {
const seconds = ref(defaultTime)
function initCountDownWorker() {
if (!countDownWorker) {
countDownWorker = new Worker(
new URL('./count-down.work.js', import.meta.url),
{ type: 'module' }
)
countDownWorker.postMessage({ type: 'ready', data: defaultTime })
countDownWorker.addEventListener('message', function (event) {
const { type, data } = event.data
switch (type) {
case 'ready':
console.log(data)
break
case 'data':
seconds.value = data
break
case 'stop':
break
case 'end':
countDownWorker?.terminate()
countDownWorker = null
break
case 'reset':
seconds.value = data
break
}
})
countDownWorker.addEventListener('error', function (event) {
console.log(event)
})
}
}
function startCountDown() {
if (!countDownWorker) {
initCountDownWorker()
}
countDownWorker?.postMessage({ type: 'start' })
}
function stopCountDown() {
countDownWorker?.postMessage({ type: 'stop' })
}
function resetCountDown() {
countDownWorker?.postMessage({ type: 'reset', data: defaultTime })
}
return { seconds, startCountDown, stopCountDown, resetCountDown }
}
count-down.work.js
let seconds = 0
let interval = null
?
function countDown() {
if (interval) {
clearInterval(interval)
}
interval = setInterval(() => {
if (seconds <= 0) {
postMessage({ type: 'end' })
return
}
seconds--
postMessage({ type: 'data', data: seconds })
}, 1000)
}
?
function stop() {
clearInterval(interval)
postMessage({ type: 'stop' })
}
?
function reset(data) {
clearInterval(interval)
seconds = data
postMessage({ type: 'reset', data: seconds })
}
?
function ready() {
postMessage({ type: 'ready', data: 'it is ready' })
}
?
addEventListener('message', function (event) {
const { type, data } = event.data
switch (type) {
case 'ready':
seconds = data
ready()
break
case 'start':
countDown()
break
case 'stop':
stop()
break
case 'reset':
reset(data)
break
}
})requestAnimationFrame方案
除了單開一個(gè)線程外,還有另外一種方案,就是requestAnimationFrame方案,因?yàn)閞equestAnimationFrame對(duì)回調(diào)函數(shù)的調(diào)用頻率通常與顯示器的刷新率相匹配,最大限度地減少了阻塞和性能問題。具體實(shí)現(xiàn)如下
import { ref, type Ref } from 'vue'
let requestAnimationFrameId: number | null = null
export default function useCountDownRAF(defaultTime: number) {
const seconds = ref(defaultTime)
function countDown(seconds: Ref<number>) {
const end = performance.now() + seconds.value * 1000
const step = () => {
const now = performance.now()
const remaining = Math.max(0, end - now)
seconds.value = Math.round(remaining / 1000)
if (remaining > 0) {
requestAnimationFrameId = requestAnimationFrame(step)
} else {
seconds.value = 0
if (requestAnimationFrameId) {
cancelAnimationFrame(requestAnimationFrameId)
requestAnimationFrameId = null
}
}
}
requestAnimationFrame(step)
}
function startCountDown() {
if (seconds.value <= 0) {
seconds.value = defaultTime
}
countDown(seconds)
}
function stopCountDown() {
if (requestAnimationFrameId) {
cancelAnimationFrame(requestAnimationFrameId)
requestAnimationFrameId = null
}
}
function resetCountDown() {
seconds.value = defaultTime
stopCountDown()
}
return { seconds, startCountDown, stopCountDown, resetCountDown }
}注意:使用requestAnimationFrame方案時(shí),當(dāng)頁面置于后臺(tái)時(shí),requestAnimationFrame回調(diào)會(huì)暫停執(zhí)行以節(jié)省性能,所以需要依托performance.now()(這個(gè)更好,Date會(huì)存在系統(tǒng)時(shí)間被篡改的風(fēng)險(xiǎn))或者Date.now()來修正倒計(jì)時(shí)時(shí)間。
到此這篇關(guān)于JavaScript如何實(shí)現(xiàn)精準(zhǔn)倒計(jì)時(shí)的文章就介紹到這了,更多相關(guān)JavaScript倒計(jì)時(shí)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript純前端實(shí)現(xiàn)語音播報(bào)和朗讀功能
這篇文章主要為大家詳細(xì)介紹了JavaScript純前端實(shí)現(xiàn)語音播報(bào)和朗讀功能的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-04-04
一文教會(huì)你如何在JavaScript中使用展開運(yùn)算符
展開運(yùn)算符(spread operator)允許一個(gè)表達(dá)式在某處展開,下面這篇文章主要給大家介紹了關(guān)于如何通過一文教會(huì)你如何在JavaScript中使用展開運(yùn)算符的相關(guān)資料,需要的朋友可以參考下2022-10-10
原生JS實(shí)現(xiàn)簡(jiǎn)單的倒計(jì)時(shí)功能示例
這篇文章主要介紹了原生JS實(shí)現(xiàn)簡(jiǎn)單的倒計(jì)時(shí)功能,涉及javascript基于定時(shí)器的日期時(shí)間運(yùn)算與頁面元素屬性動(dòng)態(tài)修改相關(guān)操作技巧,需要的朋友可以參考下2018-08-08
Bootstrap基本樣式學(xué)習(xí)筆記之按鈕(4)
篇文章主要介紹了Bootstrap學(xué)習(xí)筆記之按鈕基本樣式的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
Layui彈框中數(shù)據(jù)表格中可雙擊選擇一條數(shù)據(jù)的實(shí)現(xiàn)
這篇文章主要介紹了Layui彈框中數(shù)據(jù)表格中可雙擊選擇一條數(shù)據(jù)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
javascript firefox 自動(dòng)加載iframe 自動(dòng)調(diào)整高寬示例
iframe 自動(dòng)獲取onload高寬以及iframe 自動(dòng)加載,具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下2013-08-08
JavaScript入門系列之知識(shí)點(diǎn)總結(jié)
JavaScript 是屬于網(wǎng)絡(luò)的腳本語言。本文是小編日常收集整理些javascript入門基礎(chǔ)知識(shí),對(duì)js新手朋友非常有幫助,對(duì)js入門知識(shí)點(diǎn)感興趣的朋友一起學(xué)習(xí)吧2016-03-03

