使用定时器获取倒计时
我们项目是使用的vue框架,所以我们在开始写业务逻辑之前,可以先封装一个将倒计时拆分为时分秒的工具函数,
方便后期复用。
代码如下:
// util.js
/**
* 获取时间对象
* @param(Number) 剩余倒计时的时间戳
* @param(Boolean) 是否限制小时在24小时以内
*/
export const getDateObj = (times, isLimitHour) => {
const second = times / 1000
const leftSecond = ~~(second % 60)
const leftMinutes = ~~(second / 60 % 60)
const leftHours = isLimitHour ? ~~(second / 60 / 60) : ~~(second / 60 / 60 % 24)
const leftDay = ~~(second / 60 / 60 / 24)
return {
day: leftDay,
hours: leftHours,
minutes: leftMinutes,
second: leftSecond
}
}
工具函数实现后,我们就可以在组件中引入该函数,并根据业务需求来使用实现倒计时功能。 下边是简洁版的倒计时的实现(只有倒计时逻辑,无视图代码)
import { getDateObj } from '@/utils/util';
export default {
data() {
return {
timer: null,
times: 1122404, // 剩余时间(毫秒)
countDown: ''
}
},
mounted() {
this.setCountDown();
},
methods: {
// 设置倒计时
setCountDown() {
clearInterval(this.timer);
this.timer = setInterver(() => {
this.times -= 1000;
let { hours, mintues, second } = getDateObj(this.times);
this.countDown = hours + ':' + mintues + ':' + second;
if (!hours && !mintues && !second) { // 倒计时结束时
clearInterval(this.timer);
// 执行其他特殊操作
}
}, 1000);
}
}
}
使用一个定时器显示多个倒计时
有时一个页面可能会有多个倒计时的显示,此时如果一个倒计时对应一个定时器,不仅会增大浏览器的资源消耗,定时器过多还容易造成倒计时的混乱,在页面多次刷新等情形下倒计时会变得非常快,同时在页面销毁时清除定时器也比较麻烦。此时我们就可以只用一个定时器实现多个倒计时。
import { getDateObj } from '@/utils/util';
export default {
data() {
return {
timer: null,
list: [
{
name: '倒计时1',
endTime: 123456000,
countDown: 0
},
{
name: '倒计时2',
endTime: 126456000,
countDown: 0
}
]
}
},
mounted() {
this.setCountDown();
},
methods: {
// 设置倒计时
setCountDown() {
clearInterval(this.timer);
this.timer = setInterver(() => {
this.times -= 1000;
this.list = this.list.map(item => {
let { hours, mintues, second } = getDateObj(this.times);
item.countDown = hours + ':' + mintues + ':' + second;
if (!hours && !mintues && !second) { // 倒计时结束时
clearInterval(this.timer);
// 执行其他特殊操作
}
return item;
});
}, 1000);
}
}
}
使用web worker定义倒计时
我们知道setInterval是异步的,这就会产生一个问题,当有事件发生阻塞时,setInterval就会被卡住,倒计时就会变慢。
此时我们就可以用到 web Worker来创建一个子线程,将定时器放入子线程中执行,这样就不会受到主线程的影响而发生阻塞
或拖慢。
web worker的详细介绍,可以查看阮一峰老师的文章传送门
- 首先我们先新建一个
Wirker线程 一般情况下,子线程执行的代码需要写在一个单独的js中。如下所示
var worker = new Worker('work.js')
不过,我们也可以使用blob + URL.createObjectURL来创建一个虚拟的文件,之后再执行web worker。
function getWorker(worker, param) {
const code = woker.toString();
const blob = new Blob([`(${code})(${JSON.stringify(param)})`]);
return new Worker(URL.createObjectURL(blob));
}
- 子线程内实现倒计时 步骤如下:
- 监听主线程消息,开始和结束对应的计时时间
- 处理倒计时得到的天,时,分,秒的信息
- 每过一秒发送一次对应的信息给主线程
/**
* 生成子线程的倒计时
* @param {*} param
*/
export function setWorkCountDown(param) {
let _timer = null
let { endTime } = param
this.onmessage = e => {
const { data: { type, interval } } = e
const countDown = (milsecond) => {
const second = milsecond / 1000
if (milsecond < 0) {
clearInterval(_timer)
return {
day: 0,
hours: 0,
minutes: 0,
second: 0
}
}
const leftSecond = ~~(second % 60)
const leftMinutes = ~~(second / 60 % 60)
// const leftHours = ~~(second / 60 / 60 % 24)
const leftHours = ~~(second / 60 / 60)
const leftDay = ~~(second / 60 / 60 / 24)
// return [leftDay, leftHours, leftMinutes, leftSecond]
return {
day: leftDay,
hours: leftHours,
minutes: leftMinutes,
second: leftSecond
}
}
nextTick = countDown(endTime)
if (type === 'start') {
_timer = setInterval(() => {
// 通过postMessage告诉父进程 我已经计时了1秒了 你可以操作了
this.postMessage({ nextTick })
endTime = endTime - interval
nextTick = countDown(endTime)
}, interval)
} else if (type === 'end') {
clearInterval(_timer)
}
}
}
- 主线程启动倒计时
步骤如下:- 启动子线程
- 监听子进程倒计时时间
const worker = getWorker(setWorkCountDown, { endTime: 690200000 });
worker.postMessage({ type: 'start', 1000});
- 关闭子线程的倒计时并释放子线程的内存
worker.postMessage({ type: 'end'}); // 关闭子线程的定时器
worker.terminate(); // 释放子线程的内存
为了更好复用,在vue中,我们可以将它封装在工具函数,方便我们在项目中使用它。
完整代码如下:
// util.js
/**
* 动态生成一个web worker文件
* @param {*} worker
* @param {*} param
*/
export function getWorker(worker, param) {
const code = worker.toString()
const blob = new Blob([`(${code})(${JSON.stringify(param)})`])
return new Worker(URL.createObjectURL(blob))
}
/**
* 生成子进程的倒计时
* @param {*} param
*/
export function setWorkCountDown(param) {
let _timer = null
let { endTime } = param
this.onmessage = e => {
const { data: { type, interval } } = e
const countDown = (milsecond) => {
const second = milsecond / 1000
if (milsecond < 0) {
clearInterval(_timer)
return {
day: 0,
hours: 0,
minutes: 0,
second: 0
}
}
const leftSecond = ~~(second % 60)
const leftMinutes = ~~(second / 60 % 60)
// const leftHours = ~~(second / 60 / 60 % 24)
const leftHours = ~~(second / 60 / 60)
const leftDay = ~~(second / 60 / 60 / 24)
// return [leftDay, leftHours, leftMinutes, leftSecond]
return {
day: leftDay,
hours: leftHours,
minutes: leftMinutes,
second: leftSecond
}
}
nextTick = countDown(endTime)
if (type === 'start') {
_timer = setInterval(() => {
// 通过postMessage告诉父进程 我已经计时了1秒了 你可以操作了
this.postMessage({ nextTick })
endTime = endTime - interval
nextTick = countDown(endTime)
}, interval)
} else if (type === 'end') {
clearInterval(_timer)
}
}
}
/**
* 得到子进程的倒计时对象
* @param {*} times 倒计时时间
* @param {*} interval 间隔
*/
export function getWorkerCountDownObj(times, interval = 1000) {
const worker = getWorker(setWorkCountDown, { endTime: times });
worker.postMessage({ type: 'start', interval});
return worker;
}
import { getWorkerCountDownObj } from '@/utils/util';
export default {
data() {
return {
times: 1122404, // 剩余时间(毫秒)
countDown: '',
worker: null // web worker对象
}
},
mounted() {
this.setCountDown();
},
methods: {
// 设置倒计时
setCountDown() {
// 获取worker对象
this.worker = getWorkerCountDownObj(this.times);
this.worker.onmessage = (e) => {
// 获取子进程中的倒计时的时分秒
let { hours, minutes, second } = e.data.nextTick;
this.countDown = hours + ':' + mintues + ':' + second;
if (!hours && !mintues && !second) { // 倒计时结束时
this.worker.postMessage({ type: 'end'}); // 关闭子进程的定时器
this.worker.terminate(); // 释放子进程的内存
// 执行其他特殊操作
}
}
}
}
}
倒计时其他注意点
- pc端多页面切换,手机端锁屏,应用进入后台时定时器停滞或变慢问题的解决。
- 1.1 普通h5应用解决方法
使用document对象上新增
document.visibilityState属性来判断页面的可见性,根据页面可见性重新校准时间。
export default {
mounted() {
// 监听页面可见性事件
document.addEventListener('visibilitychange', this.visibilitychange, false);
},
destroyed() {
document.removeEventListener('visibilitychange', this.visibilitychange);
},
methods: {
visibilitychange() {
// 用户离开了当前页面
if (document.visibilityState === 'hidden') {
clearIntervar(this.timer);
}
// 用户打开或回到页面
if (document.visibilityState === 'visible') {
clearIntervar(this.timer);
this.setCountDown();
}
},
// 倒计时方法
setCountDown() {}
}
}
document.visibilityState的详细介绍和使用可以看阮一峰老师的一篇文章传送门
- 1.2 小程序内嵌网页(webview)
在小程序内嵌网页(webview)应用中使用document.visibilityState属性无法监听手机端锁屏,此时我们可以使用 微信提供的API来监听页面的是否在前台.
export default {
mounted() {
// 监听页面是否在前台
WeixinJSBridge.on('onPageStateChange', this.visibilitychange);
},
methods: {
visibilitychange() {
if (res.active) {
this.setCountDown();
} else {
clearInterval(this.timer);
}
},
// 倒计时方法
setCountDown() {}
}
}
详细介绍请看微信小程序官方文档