基于RequestAnimationFrame实现高精度毫秒级正向计时器

背景

最近做了一个周末嘉年华的活动【免费领取「王者荣耀千元账号」】,效果图如下。玩法也很简单:点击开始,计时器开始计时,点击停止,点击开始按钮后会变成停止,当计时结束时,秒表显示时间为 10:00 时,即可获取 「价值千元的王者荣耀账号」!

网站建设哪家好,找成都创新互联公司!专注于网页设计、网站建设、微信开发、微信平台小程序开发、集团企业网站建设等服务项目。为回馈新老客户创新互联还提供了呼伦贝尔免费建站欢迎大家使用!

编组

点我体验 !!!

若遇到活动未开始或者活动结束,可以前往转转app搜索【游戏】即可参与更多活动,各种福利拿到手软!

需求分析

从图上可以看出来,核心就是一个正向计时器。通过js实现一个普通的正向计时器很简单,大多数想到都是使用setInterval来实现。那么还有没有其他的实现方式呢?又怎么去实现一个高精度的毫秒级正向计时器呢?

最近看了vant4的倒计时组件的源码,发现其并没有使用setInterval, 而是封装了requestAnimationFrame 和利用 Date.now()来处理毫秒级渲染和倒计时实现。那么能不能通过requestAnimationFrame来实现一个正向计时器呢?

先看看效果图,接下来将会一步步去实现:

体验地址: https://suyxh.github.io/timer-demo/

setInterval版

首先呢,来看看使用setInterval是如何实现的。在网上看了很多文章,大多都是使用的 setInterval 去实现,大致效果如下:

setinterval

从效果图上我们可以发现,最后一位始终为0,甚至还是有些小bug,很明显不是我们想要的。具体代码如下:








Document
















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() 开始去执行回调函数的时刻。

那我们直接使用该值不就可以了吗?试试看:








Document



hello world

这里显示倒计时状态









效果如下:

测试版

虽然比较简陋,但是并没有出现 setInterval版 的bug,接下来在一步步优化。

简易版

我们加上格式化时间的函数 parseTime() 和 parseFormat(), 代码如下:








Document



hello world

这里显示倒计时状态









效果如下:

简易版

又看到了我们熟悉的时间格式啦, 格式化的方法也是来源于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()的返回值相同;在实现暂停、继续时,需要计算一下补时时间。

分享文章:基于RequestAnimationFrame实现高精度毫秒级正向计时器
分享链接:http://www.mswzjz.cn/qtweb/news11/212311.html

攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能