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)來(lái)處理。然而,如果在計(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ù)載過(guò)高,也可能導(dǎo)致計(jì)時(shí)器的回調(diào)函數(shù)被延遲執(zhí)行。
知道了原因,問(wèn)題就比較好解決了。
既然由于js是單線程機(jī)制,那我們就專門開(kāi)一個(gè)線程來(lái)進(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方案
除了單開(kāi)一個(gè)線程外,還有另外一種方案,就是requestAnimationFrame方案,因?yàn)閞equestAnimationFrame對(duì)回調(diào)函數(shù)的調(diào)用頻率通常與顯示器的刷新率相匹配,最大限度地減少了阻塞和性能問(wèn)題。具體實(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)頁(yè)面置于后臺(tái)時(shí),requestAnimationFrame回調(diào)會(huì)暫停執(zhí)行以節(jié)省性能,所以需要依托performance.now()(這個(gè)更好,Date會(huì)存在系統(tǒng)時(shí)間被篡改的風(fēng)險(xiǎn))或者Date.now()來(lái)修正倒計(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ù)組重復(fù)內(nèi)容的兩種方法(推薦)
本文給大家介紹兩種JavaScript判斷數(shù)組重復(fù)內(nèi)容的方法(推薦)非常不錯(cuò)具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-06-06
如何在selenium中使用js實(shí)現(xiàn)定位
這篇文章主要介紹了如何在selenium中使用js實(shí)現(xiàn)定位,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
微信小程序新增的拖動(dòng)組件movable-view使用教程
這篇文章主要給大家介紹了微信小程序最近新增的拖動(dòng)組件movable-view的簡(jiǎn)單使用教程,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-05-05
利用Javascript開(kāi)發(fā)一個(gè)二維周視圖日歷
這篇文章主要給大家介紹了關(guān)于利用Javascript如何開(kāi)發(fā)一個(gè)二維周視圖日歷的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12
js實(shí)現(xiàn)帶搜索功能的下拉框?qū)崟r(shí)搜索實(shí)時(shí)匹配
當(dāng)select輸入框中每輸入一點(diǎn)內(nèi)容的時(shí)候,在option中找出與內(nèi)容匹配的選項(xiàng)顯示在option的前面選項(xiàng)中,下面有個(gè)不錯(cuò)的示例,希望朋友們可以喜歡2013-11-11

