js倒计时的三种写法
最编程
2024-02-23 15:39:22
...
1、requestAnimationFrame
通常是每秒 60 次,在大多数遵循 W3C 建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配,时间间隔小于16ms不适用
chrome和手机上实验,页面即使在后台,requestAnimationFrame也在继续执行
(function() {
// requestAnimationFrame
let requestAnimationFrameTimer = 0
let start = new Date().getTime(); //获取开始时间
let timeout = 1000
let agoTime = 0
let countIndex = 0
function showTime(timeDiff) { // 剩余时间
let end = new Date().getTime(); //获取当前时间
agoTime = end - start
if (agoTime >= countIndex * timeout) { // 大于1s
if(countIndex > 0) {
timeDiff -= timeout
}
countIndex++
if (timeDiff <= 0) {
window.cancelAnimationFrame(requestAnimationFrameTimer)
return
}
let temp = Math.round(timeDiff / 1000)
//获取还剩多少天
let day = Math.floor(temp / 60 / 60 / 24);
//获取还剩多少小时
let hour = Math.floor((temp / 60 / 60) % 24);
//获取还剩多少分钟
let minute = Math.floor((temp / 60) % 60);
//获取还剩多少秒
let second = Math.floor(temp % 60);
//给小于10的数值前面加 0
function timerFilter(params) {
if (params - 0 < 10) {
return "0" + params;
} else {
return params;
}
}
//输出还剩多少时间
let str = day + "天" + timerFilter(hour) + ":" + timerFilter(minute) + ":" + timerFilter(second)
console.log(str);
// document.getElementById('time1').innerHTML = str
}
requestAnimationFrameTimer = window.requestAnimationFrame(() => {
showTime(timeDiff);
});
}
showTime(10000);
}());
2、setTimeout
- setTimeout/setInterval回调触发超时的原因
- 使用setInterval要确保执行持续时间短于间隔频率,计时列队机制是:假如我设置时间间隔是10ms,setInterval是上一个任务开始后10ms 开始执行任务列队的下一个任务。如果上一个任务用了15ms 才执行完,下一个任务不会在第25ms 处执行,而是会在15ms 处立刻执行,为了确保两次执行之间有固定的间隔,建议用setTimeout而不是setInterval
// 使用setTimeout
;(function(){
let timer = ''
let timeDiff = 0
let startTime = 0
let countIndex = 1
let timeout = 1000
function showTime(timeDiff, interval) {
timer && clearTimeout(timer);
if (timeDiff <= 0) {
timer && clearTimeout(timer)
timeDiff = 0
startTime = 0
countIndex = 1
return
}
//获取还剩多少天
let day = Math.floor(timeDiff / 1000 / 60 / 60 / 24);
//获取还剩多少小时
let hour = Math.floor((timeDiff / 1000 / 60 / 60) % 24);
//获取还剩多少分钟
let minute = Math.floor((timeDiff / 1000 / 60) % 60);
//获取还剩多少秒
let second = Math.floor((timeDiff / 1000) % 60);
//输出还剩多少时间
let str = day + "天" + timerFilter(hour) + ":" + timerFilter(minute) + ":" + timerFilter(second)
console.log(str);
// document.getElementById('time2').innerHTML = str
//给小于10的数值前面加 0
function timerFilter(params) {
if (params - 0 < 10) {
return "0" + params;
} else {
return params;
}
}
timer = setTimeout(() => {
const endTime = new Date().getTime();
const difference = endTime - (startTime + countIndex * timeout)
console.log(`${countIndex}: 偏差${difference}ms`);
countIndex++
showTime(timeDiff - 1000, timeout - difference)
}, interval)
}
startTime = new Date() * 1
showTime(10000, interval = timeout);
})()
3、web worker
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程
;(function() {
// web worker
function getWorker(worker, param) {
const code = worker.toString()
const blob = new Blob([`(${code})(${JSON.stringify(param)})`])
return new Worker(URL.createObjectURL(blob))
}
function getCountDown(param) {
let _timer = null
let {
leftTime
} = param
this.onmessage = e => {
const {
data: {
type,
interval
}
} = e
const countDown = (milsecond) => {
const second = milsecond / 1000
if (milsecond <= 0) {
clearInterval(_timer)
return [0, 0, 0, 0]
}
// ~是js里的按位取反操作符 , ~~ 就是执行两次按位取反,其实就是保持原值,但是注意虽然是原值
// 但是对布尔型变量执行这个操作,会转化成相应的数值型变量,也就是 ~~true === 1
const leftSecond = ~~(second % 60)
const leftMinutes = ~~(second / 60 % 60)
const leftHours = ~~(second / 60 / 60 % 24)
const leftDay = ~~(second / 60 / 60 / 24)
return [leftDay, leftHours, leftMinutes, leftSecond]
}
nextTick = countDown(leftTime)
if (type === 'start') {
this.postMessage({
nextTick
})
_timer = setInterval(() => {
leftTime = leftTime - interval
nextTick = countDown(leftTime)
// 通过postMessage告诉父进程 我已经计时了1秒了 你可以操作了
if (leftTime > 0) {
this.postMessage({
nextTick
})
}
}, interval)
} else if (type === 'end') {
clearInterval(_timer)
}
}
}
const worker = getWorker(getCountDown, {
leftTime: 10000
})
worker.postMessage({
type: "start",
interval: 1000
})
worker.onmessage = e => render()(e.data.nextTick)
// 渲染方法
function render() {
const elem = document.getElementById('time3')
return function(params = [0, 0, 0, 0]) {
temp = params.slice(1).map(item => {
return item < 10 ? '0' + item : item
})
let str = params[0] + '天' + temp.join(':')
console.log('倒计时:', str);
// elem.innerHTML = str
}
}
})()
思考
可以采用后端返回剩余时间,前端定时请求接口获取剩余时间避免误差
参考
JavaScript 倒计时踩坑集锦
JS倒计时setTimeout为什么会出现误差
利用 requestAnimationFrame 实现 setInterval 计时器
日常开发: Web Worker实现秒杀倒计时
如何使倒计时计时器的性能达到最优?
vue实现倒计时的代码(纯代码)