成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

基于RequestAnimationFrame實現高精度毫秒級正向計時器

開發 前端
正向毫秒級計時器主要就是利用了??window.requestAnimationFrame??的回調函數的參數為??DOMHighResTimeStamp??,且與??performance.now()??的返回值相同;在實現暫停、繼續時,需要計算一下補時時間。

背景

最近做了一個周末嘉年華的活動【免費領取「王者榮耀千元賬號」】,效果圖如下。玩法也很簡單:點擊開始,計時器開始計時,點擊停止,點擊開始按鈕后會變成停止,當計時結束時,秒表顯示時間為 10:00 時,即可獲取 「價值千元的王者榮耀賬號」!

圖片

編組

點我體驗 !!!

若遇到活動未開始或者活動結束,可以前往轉轉app搜索【游戲】即可參與更多活動,各種福利拿到手軟!

需求分析

從圖上可以看出來,核心就是一個正向計時器。通過js實現一個普通的正向計時器很簡單,大多數想到都是使用setInterval來實現。那么還有沒有其他的實現方式呢?又怎么去實現一個高精度的毫秒級正向計時器呢?

最近看了vant4的倒計時組件的源碼,發現其并沒有使用setInterval, 而是封裝了requestAnimationFrame 和利用 Date.now()來處理毫秒級渲染和倒計時實現。那么能不能通過requestAnimationFrame來實現一個正向計時器呢?

先看看效果圖,接下來將會一步步去實現:

圖片

體驗地址: https://suyxh.github.io/timer-demo/

setInterval版

首先呢,來看看使用setInterval是如何實現的。在網上看了很多文章,大多都是使用的 setInterval 去實現,大致效果如下:

圖片

setinterval

從效果圖上我們可以發現,最后一位始終為0,甚至還是有些小bug,很明顯不是我們想要的。具體代碼如下:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" cnotallow="IE=edge">
<meta name="viewport" cnotallow="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<input type="text" id="timetext" value="00時00分00秒" readonly>
<br>
<br>
<button type="button" notallow="start()">開始</button>
<button type="button" notallow="stop()">暫停</button>
<button type="button" notallow="Reset()">重置</button>


<script>
//初始化變量
let hour, minute, second;//時 分 秒
hour = minute = second = 0;//初始化
let millisecond = 0;//毫秒
let int;
//重置函數
function Reset () {
window.clearInterval(int);
millisecond = hour = minute = second = 0;
document.getElementById('timetext').value = '00時00分00秒000毫秒';
}
//開始函數
function start () {
int = setInterval(timer, 50);//每隔50毫秒執行一次timer函數
}
//計時函數
function timer () {
millisecond = millisecond + 50;
if (millisecond >= 1000) {
millisecond = 0;
second = second + 1;
}
if (second >= 60) {
second = 0;
minute = minute + 1;
}

if (minute >= 60) {
minute = 0;
hour = hour + 1;
}
document.getElementById('timetext').value = hour + '時' + minute + '分' + second + '秒' + millisecond + '毫秒';

}
//暫停函數
function stop () {
window.clearInterval(int);
}
</script>
</body>

</html>

requestAnimationFrame版

上文中提到vant的CutDown組件,主要就是利用 Date.now() 會自己走的原理,結合 requestAnimationFrame 去做時間計算;那么正向計時器則是利用了 requestAnimationFrame 回調函數的參數去做時間計算,從而實現毫秒級的計時器。

「window.requestAnimationFrame()」 告訴瀏覽器——你希望執行一個動畫,并且要求瀏覽器在下次重繪之前調用指定的回調函數更新動畫。該方法需要傳入一個回調函數作為參數,該回調函數會在瀏覽器下一次重繪之前執行,當你準備更新動畫時你應該調用此方法。這將使瀏覽器在下一次重繪之前調用你傳入給該方法的動畫函數 (即你的回調函數)。


「注意:」 若你想在瀏覽器下次重繪之前繼續更新下一幀動畫,那么回調函數自身必須再次調用 window.requestAnimationFrame()

MDN requestAnimationFrame

「參數」

  • callback?下一次重繪之前更新動畫幀所調用的函數 (即上面所說的回調函數)。該回調函數會被傳入DOMHighResTimeStamp參數,該參數與performance.now()的返回值相同,它表示requestAnimationFrame() 開始去執行回調函數的時刻。

「返回值」

一個 long 整數,請求 ID,是回調列表中唯一的標識。是個非零值,沒別的意義。你可以傳這個值給 window.cancelAnimationFrame() 以取消回調函數。

測試版

通過 requestAnimationFrame API可以知道,回調函數中的參數就是一個 DOMHighResTimeStamp參數,該參數與performance.now()的返回值相同,它表示requestAnimationFrame() 開始去執行回調函數的時刻。

那我們直接使用該值不就可以了嗎?試試看:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" cnotallow="IE=edge">
<meta name="viewport" cnotallow="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<div id="app">hello world</div>
<div id="status">這里顯示倒計時狀態</div>
<button class="start">開始</button>

<br />

<script>
const render = (time) => {
document.querySelector("#status").innerHTML = Math.floor(time) / 1000
}

const useCountUp = () {
let rafId;
let endTime;

const step = (timestamp) => {
console.log('timestamp', timestamp)
render(timestamp)
rafId = window.requestAnimationFrame(step)
}

const start = () {
rafId = window.requestAnimationFrame(step)
}

return {
start,
}
}

const { start } = useCountUp();

document.querySelector('.start').addEventListener('click', () => {
start()
})

</script>
</body>

</html>

效果如下:

圖片

測試版

雖然比較簡陋,但是并沒有出現 setInterval版 的bug,接下來在一步步優化。

簡易版

我們加上格式化時間的函數 parseTime() 和 parseFormat(), 代碼如下:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" cnotallow="IE=edge">
<meta name="viewport" cnotallow="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<div id="app">hello world</div>
<div id="status">這里顯示倒計時狀態</div>
<button class="start">開始</button>

<br />

<script>
/**
* @description: 補0操作
* @param {*} num
* @param {*} targetLength
* @return {*}
*/
function padZero (num, targetLength = 2) {
let str = num + ''

while (str.length < targetLength) {
str = '0' + str
}

return str
}

/**
* @description: 解析時間
* @param {*} time
* @return {*}
*/
function parseTime (time) {
const SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR

const days = Math.floor(time / DAY)
const hours = Math.floor((time % DAY) / HOUR)
const minutes = Math.floor((time % HOUR) / MINUTE)
const seconds = Math.floor((time % MINUTE) / SECOND)
const milliseconds = Math.floor(time % SECOND)

return {
total: time,
days,
hours,
minutes,
seconds,
milliseconds,
}
}


/**
* @description: 格式化時間
* @param {*} format
* @param {*} currentTime
* @return {*}
*/
function parseFormat (format, currentTime) {
const { days } = currentTime
let { hours, minutes, seconds, milliseconds } = currentTime

if (format.includes('DD')) {
format = format.replace('DD', padZero(days))
} else {
hours += days * 24
}

if (format.includes('HH')) {
format = format.replace('HH', padZero(hours))
} else {
minutes += hours * 60
}

if (format.includes('mm')) {
format = format.replace('mm', padZero(minutes))
} else {
seconds += minutes * 60
}

if (format.includes('ss')) {
format = format.replace('ss', padZero(seconds))
} else {
milliseconds += seconds * 1000
}

if (format.includes('S')) {
const ms = padZero(milliseconds, 3)

if (format.includes('SSS')) {
format = format.replace('SSS', ms)
} else if (format.includes('SS')) {
format = format.replace('SS', ms.slice(0, 2))
} else {
format = format.replace('S', ms.charAt(0))
}
}

return format
}


/**
* @description: 渲染時間
* @param {*} time
* @return {*}
*/
const render = (time) => {
time = parseFormat('HH:mm:ss:SSS', parseTime(time))
document.querySelector("#status").innerHTML = time
}


const useCountUp = () {
let rafId;
let endTime;

const step = (timestamp) => {
console.log('timestamp', timestamp)
// render(timestamp)
rafId = window.requestAnimationFrame(step)
}

const start = () {
rafId = window.requestAnimationFrame(step)
}

return {
start,
}
}

const { start } = useCountUp();

document.querySelector('.start').addEventListener('click', () => {
start()
})

</script>
</body>

</html>

效果如下:

圖片

簡易版

又看到了我們熟悉的時間格式啦, 格式化的方法也是來源于vant的CutDown組件中的格式化代碼!

格式化雖然是完成了,但是怎么去停止呢?能不能支持暫停、繼續、重置呢?

接下來繼續完善。

進階版

我們直接通過 window.cancelAnimationFrame() 去取消回調函數即可!在 useCountUp函數中添加一下 pause 即可!

const pause = () {
if (rafId) {
window.cancelAnimationFrame(rafId)
}
}

效果如下:

圖片

進階版

不少的小伙伴已經發現,停止雖然是沒問題了,當再次點擊開始的時候,時間怎么不對了?有瑕疵!

因為我們少算補時時間,做如下修改,添加startTime 、 stopTime 和  goOn 方法:

const useCountUp = () {
let rafId;
let startTime;
let stopTime;


const step = (timestamp) => {
console.log('timestamp', timestamp)
render(timestamp - startTime)
rafId = window.requestAnimationFrame(step)
}

const start = () {
startTime = performance.now()
rafId = window.requestAnimationFrame(step)
}

const pause = () {
stopTime = performance.now()
if (rafId) {
window.cancelAnimationFrame(rafId)
}
}

const goOn = () {
startTime += performance.now() - stopTime
rafId = window.requestAnimationFrame(step)
}

return {
start,
pause,
goOn
}
}

這里基本上已經完成了暫停和繼續的功能了,但是仍是有些bug的,可以多次點擊繼續試試?? 。

完整版

接下來,我們來修復上述的bug,方法:添加一個變量來表示當前計時器的狀態。

在增加幾個新功能:

  • 添加 重置 方法: 其實我們只需要調用一下暫停,在清理一下時間即可
  • 支持 配置:比如 正香計時的時間, 計時結束的函數

核心代碼如下,其他部分代碼不變:

const useCountUp = (options) => {
let rafId, startTime, stopTime, curentTime, counting = false

const step = (timestamp) => {
curentTime = timestamp - startTime
render(curentTime)
options.onChange?.(curentTime);

if (options.time) {
if (Math.floor(curentTime) < options.time) {
rafId = window.requestAnimationFrame(step)
} else {
pause()
options.onFinish?.()
}
} else {
rafId = window.requestAnimationFrame(step)
}

}

const start = () {
// 計時中 或者 已經開始過計時想要重新開始計時,應該先點擊一下 重置 再開始計時
if (counting || curentTime) {
return
}
counting = true
startTime = performance.now()
rafId = window.requestAnimationFrame(step)
}

const pause = () {
// 已經暫停后,屏蔽掉點擊
if (!counting) {
return
}
counting = false
stopTime = performance.now()
if (rafId) {
window.cancelAnimationFrame(rafId)
}
}

const goOn = () {
// 已經在計時中,屏蔽掉點擊
if (counting) {
return
}
counting = true
startTime += performance.now() - stopTime
rafId = window.requestAnimationFrame(step)
}

const reset = () {
pause()
curentTime = 0
startTime = 0
stopTime = 0
render(0)
}

return {
start,
pause,
goOn,
reset
}
}

const { start, pause, goOn, reset } = useCountUp({
time: 3 * 1000,
onChange: current console.log('change', current),
onFinish: () console.log('finish'),
});

document.querySelector('.start').addEventListener('click', () => {
start()
})

document.querySelector('.pause').addEventListener('click', () => {
pause()
})

document.querySelector('.goOn').addEventListener('click', () => {
goOn()
})

document.querySelector('.reset').addEventListener('click', () => {
reset()
})

到此基本上就是實現了一個毫秒級的正向計時器!

vue版

只是對js的邏輯進行了一些封裝

代碼:https://github.com/SuYxh/timer-demo

預覽:https://suyxh.github.io/timer-demo/

總結

正向毫秒級計時器主要就是利用了window.requestAnimationFrame的回調函數的參數為DOMHighResTimeStamp,且與performance.now()的返回值相同;在實現暫停、繼續時,需要計算一下補時時間。

責任編輯:武曉燕 來源: 大轉轉FE
相關推薦

2013-05-23 16:01:47

Android開發移動開發Chronometer

2023-04-17 09:08:27

CSS計時器

2011-05-31 16:50:35

Android 線程

2012-05-08 13:58:37

SharePoint

2021-11-26 00:04:20

Go計時器重構

2011-09-08 14:01:01

Android Wid實例

2013-03-25 10:03:35

網絡優化網絡抑制快速認知網絡

2020-06-11 08:48:49

JavaScript開發技術

2022-06-28 15:29:56

Python編程語言計時器

2023-12-11 09:50:35

Linux定時器

2010-01-05 15:00:30

.NET Framew

2010-01-25 11:29:33

Android計時器

2022-06-23 07:23:34

自定義組件計時器

2020-03-10 09:42:04

JavaScript前端線程

2021-01-18 09:39:35

室內定位技術物聯網

2022-06-30 16:10:26

Python計時器裝飾器

2021-12-07 11:30:32

Go煮蛋計時器

2019-12-24 16:52:22

Go語言騰訊TM函數

2011-04-21 10:49:28

Linux時間定時器

2024-07-18 08:46:58

.NET輕量級計時器測量代碼塊
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 九九久久精品 | 欧美激情亚洲天堂 | 国产91一区| 色精品视频 | 欧美一二三区 | 国产一区视频在线 | 日产精品久久久一区二区福利 | 久久久.com | 欧美三区在线观看 | 91在线一区 | 国产成人精品免费视频 | 91精品国产91久久久久久密臀 | 四虎海外 | 中文一区二区 | 亚洲一区在线日韩在线深爱 | 国产成人综合一区二区三区 | 亚洲精品播放 | 99re国产视频 | 日韩中文在线视频 | 成人免费视频网站 | 久色视频在线 | 精品国产乱码久久久久久蜜臀 | 国产精品久久久久免费 | 精品伦精品一区二区三区视频 | 中文字幕在线一区 | 日韩国产一区二区三区 | 99精品99| 午夜欧美 | 午夜免费福利片 | 亚洲欧美日韩一区二区 | 国产精品黄视频 | 国产99热精品 | 精品视频一区二区在线观看 | 一区视频在线免费观看 | 午夜理伦三级理论三级在线观看 | 亚洲色欧美另类 | 狠狠操狠狠操 | 91精品国产91久久久久青草 | 欧美激情综合 | 日韩精品一二三区 | 视频一区在线观看 |