由于定时器精准度问题,根据设备性能的影响误差会存在 0~20ms
,所以需要结合自身的使用场景来决定能不能使用。
此处实现使用的是 setInterval
定时器加上 performance.now()
来校准 setInterval
的误差。
以下例举出了适用于各个框架的版本,可根据需求自行修改。
Vue版本
import { reactive } from 'vue';
export interface TimeStr {
m : string;
s : string;
ms : string;
}
export const state = reactive<{
timer : NodeJS.Timeout | null;
time : TimeStr;
}>({
timer: null,
time: {
m: '00',
s: '00',
ms: '000'
}
});
/**
* 获取高精度当前时间戳
*/
const getCurrentTime = () => {
return performance.now();
};
/**
* 计算时间差
* @param startTime 开始时间戳
* @param endTime 结束时间戳
* @param countTime 总时长
*/
const diffTime = (startTime : number, endTime : number, countTime : number) : TimeStr => {
const calculateTime = (time : number) => {
const totalSeconds = time / 1000;
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0');
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0');
const milliseconds = Math.round((totalSeconds % 1) * 1000).toString().padStart(3, '0');
return { m: minutes, s: seconds, ms: milliseconds };
};
const duration = endTime - startTime;
if (duration >= countTime) {
return calculateTime(countTime);
}
return calculateTime(duration);
};
/**
* 开始计时
* @param limitTime 可选的计时时间(毫秒)
* @param callback 可选的计时结束回调函数
*/
export const timing = (limitTime ?: number, callback ?: () => void) => {
if (state.timer) {
clearInterval(state.timer);
}
const startTime = getCurrentTime();
state.timer = setInterval(() => {
const currentTime = getCurrentTime();
const elapsed = currentTime - startTime;
if (limitTime !== undefined && elapsed >= limitTime) {
stop(() => {
callback && callback();
console.log('计时已结束');
});
} else {
const diff = limitTime !== undefined ? diffTime(startTime, currentTime, limitTime) : diffTime(startTime, currentTime, Number.MAX_SAFE_INTEGER);
state.time = diff;
}
}, 1);
};
/**
* 开始倒计时
* @param countdownTime 倒计时时间(毫秒)
* @param callback 倒计时结束回调
*/
export const down = (countdownTime : number, callback ?: () => void) => {
// 设置初始倒计时
state.time = diffTime(0, countdownTime, countdownTime);
if (state.timer) {
clearInterval(state.timer);
}
const startTime = getCurrentTime();
state.timer = setInterval(() => {
const currentTime = getCurrentTime();
const elapsed = currentTime - startTime;
const remainingTime = countdownTime - elapsed;
if (remainingTime <= 0) {
stop(() => {
state.time = { m: '00', s: '00', ms: '000' };
callback && callback();
console.log('倒计时已结束');
});
} else {
state.time = diffTime(0, remainingTime, countdownTime);
}
}, 1);
};
/**
* 重置计时器
*/
export const reset = (callback ?: () => void) => {
if (state.timer) {
clearInterval(state.timer);
state.timer = null;
}
state.time = { m: '00', s: '00', ms: '000' };
callback && callback()
console.log('计时器已重置');
};
/**
* 停止计时
* @param callback 停止计时回调函数
*/
export const stop = (callback ?: (time : TimeStr) => void) => {
if (state.timer) {
clearInterval(state.timer);
state.timer = null;
callback && callback(state.time);
}
};
React版本
import React, { useState, useRef, useEffect } from 'react';
export interface TimeStr {
m: string;
s: string;
ms: string;
}
const useTimer = () => {
const [time, setTime] = useState<TimeStr>({ m: '00', s: '00', ms: '000' });
const timerRef = useRef<NodeJS.Timeout | null>(null);
const getCurrentTime = () => performance.now();
const diffTime = (startTime: number, endTime: number, countTime: number): TimeStr => {
const calculateTime = (time: number) => {
const totalSeconds = time / 1000;
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0');
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0');
const milliseconds = Math.round((totalSeconds % 1) * 1000).toString().padStart(3, '0');
return { m: minutes, s: seconds, ms: milliseconds };
};
const duration = endTime - startTime;
if (duration >= countTime) {
return calculateTime(countTime);
}
return calculateTime(duration);
};
const timing = (limitTime?: number, callback?: () => void) => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
const startTime = getCurrentTime();
timerRef.current = setInterval(() => {
const currentTime = getCurrentTime();
const elapsed = currentTime - startTime;
if (limitTime !== undefined && elapsed >= limitTime) {
stop(() => {
callback && callback();
console.log('计时已结束');
});
} else {
const diff = limitTime !== undefined ? diffTime(startTime, currentTime, limitTime) : diffTime(startTime, currentTime, Number.MAX_SAFE_INTEGER);
setTime(diff);
}
}, 1);
};
const down = (countdownTime: number, callback?: () => void) => {
setTime(diffTime(0, countdownTime, countdownTime));
if (timerRef.current) {
clearInterval(timerRef.current);
}
const startTime = getCurrentTime();
timerRef.current = setInterval(() => {
const currentTime = getCurrentTime();
const elapsed = currentTime - startTime;
const remainingTime = countdownTime - elapsed;
if (remainingTime <= 0) {
stop(() => {
setTime({ m: '00', s: '00', ms: '000' });
callback && callback();
console.log('倒计时已结束');
});
} else {
setTime(diffTime(0, remainingTime, countdownTime));
}
}, 1);
};
const reset = (callback?: () => void) => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
setTime({ m: '00', s: '00', ms: '000' });
callback && callback();
console.log('计时器已重置');
};
const stop = (callback?: (time: TimeStr) => void) => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
callback && callback(time);
}
};
return { time, timing, down, reset, stop };
};
export default useTimer;
Angular版本
import { Injectable } from '@angular/core';
export interface TimeStr {
m: string;
s: string;
ms: string;
}
@Injectable({
providedIn: 'root'
})
export class TimerService {
time: TimeStr = { m: '00', s: '00', ms: '000' };
timer: any = null;
getCurrentTime(): number {
return performance.now();
}
diffTime(startTime: number, endTime: number, countTime: number): TimeStr {
const calculateTime = (time: number) => {
const totalSeconds = time / 1000;
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0');
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0');
const milliseconds = Math.round((totalSeconds % 1) * 1000).toString().padStart(3, '0');
return { m: minutes, s: seconds, ms: milliseconds };
};
const duration = endTime - startTime;
if (duration >= countTime) {
return calculateTime(countTime);
}
return calculateTime(duration);
}
timing(limitTime?: number, callback?: () => void): void {
if (this.timer) {
clearInterval(this.timer);
}
const startTime = this.getCurrentTime();
this.timer = setInterval(() => {
const currentTime = this.getCurrentTime();
const elapsed = currentTime - startTime;
if (limitTime !== undefined && elapsed >= limitTime) {
this.stop(() => {
callback && callback();
console.log('计时已结束');
});
} else {
const diff = limitTime !== undefined ? this.diffTime(startTime, currentTime, limitTime) : this.diffTime(startTime, currentTime, Number.MAX_SAFE_INTEGER);
this.time = diff;
}
}, 1);
}
down(countdownTime: number, callback?: () => void): void {
this.time = this.diffTime(0, countdownTime, countdownTime);
if (this.timer) {
clearInterval(this.timer);
}
const startTime = this.getCurrentTime();
this.timer = setInterval(() => {
const currentTime = this.getCurrentTime();
const elapsed = currentTime - startTime;
const remainingTime = countdownTime - elapsed;
if (remainingTime <= 0) {
this.stop(() => {
this.time = { m: '00', s: '00', ms: '000' };
callback && callback();
console.log('倒计时已结束');
});
} else {
this.time = this.diffTime(0, remainingTime, countdownTime);
}
}, 1);
}
reset(callback?: () => void): void {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
this.time = { m: '00', s: '00', ms: '000' };
callback && callback();
console.log('计时器已重置');
}
stop(callback?: (time: TimeStr) => void): void {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
callback && callback(this.time);
}
}
}
纯Typescript版本
export interface TimeStr {
m: string;
s: string;
ms: string;
}
class Timer {
time: TimeStr = { m: '00', s: '00', ms: '000' };
timer: NodeJS.Timeout | null = null;
getCurrentTime(): number {
return performance.now();
}
diffTime(startTime: number, endTime: number, countTime: number): TimeStr {
const calculateTime = (time: number) => {
const totalSeconds = time / 1000;
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0');
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0');
const milliseconds = Math.round((totalSeconds % 1) * 1000).toString().padStart(3, '0');
return { m: minutes, s: seconds, ms: milliseconds };
};
const duration = endTime - startTime;
if (duration >= countTime) {
return calculateTime(countTime);
}
return calculateTime(duration);
}
timing(limitTime?: number, callback?: () => void): void {
if (this.timer) {
clearInterval(this.timer);
}
const startTime = this.getCurrentTime();
this.timer = setInterval(() => {
const currentTime = this.getCurrentTime();
const elapsed = currentTime - startTime;
if (limitTime !== undefined && elapsed >= limitTime) {
this.stop(() => {
callback && callback();
console.log('计时已结束');
});
} else {
const diff = limitTime !== undefined ? this.diffTime(startTime, currentTime, limitTime) : this.diffTime(startTime, currentTime, Number.MAX_SAFE_INTEGER);
this.time = diff;
}
}, 1);
}
down(countdownTime: number, callback?: () => void): void {
this.time = this.diffTime(0, countdownTime, countdownTime);
if (this.timer) {
clearInterval(this.timer);
}
const startTime = this.getCurrentTime();
this.timer = setInterval(() => {
const currentTime = this.getCurrentTime();
const elapsed = currentTime - startTime;
const remainingTime = countdownTime - elapsed;
if (remainingTime <= 0) {
this.stop(() => {
this.time = { m: '00', s: '00', ms: '000' };
callback && callback();
console.log('倒计时已结束');
});
} else {
this.time = this.diffTime(0, remainingTime, countdownTime);
}
}, 1);
}
reset(callback?: () => void): void {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
this.time = { m: '00', s: '00', ms: '000' };
callback && callback();
console.log('计时器已重置');
}
stop(callback?: (time: TimeStr) => void): void {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
callback && callback(this.time);
}
}
}
export default Timer;
纯Js版本
class Timer {
constructor() {
this.time = { m: '00', s: '00', ms: '000' };
this.timer = null;
}
getCurrentTime() {
return performance.now();
}
diffTime(startTime, endTime, countTime) {
const calculateTime = (time) => {
const totalSeconds = time / 1000;
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0');
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0');
const milliseconds = Math.round((totalSeconds % 1) * 1000).toString().padStart(3, '0');
return { m: minutes, s: seconds, ms: milliseconds };
};
const duration = endTime - startTime;
if (duration >= countTime) {
return calculateTime(countTime);
}
return calculateTime(duration);
}
timing(limitTime, callback) {
if (this.timer) {
clearInterval(this.timer);
}
const startTime = this.getCurrentTime();
this.timer = setInterval(() => {
const currentTime = this.getCurrentTime();
const elapsed = currentTime - startTime;
if (limitTime !== undefined && elapsed >= limitTime) {
this.stop(() => {
if (callback) callback();
console.log('计时已结束');
});
} else {
const diff = limitTime !== undefined ? this.diffTime(startTime, currentTime, limitTime) : this.diffTime(startTime, currentTime, Number.MAX_SAFE_INTEGER);
this.time = diff;
}
}, 1);
}
down(countdownTime, callback) {
this.time = this.diffTime(0, countdownTime, countdownTime);
if (this.timer) {
clearInterval(this.timer);
}
const startTime = this.getCurrentTime();
this.timer = setInterval(() => {
const currentTime = this.getCurrentTime();
const elapsed = currentTime - startTime;
const remainingTime = countdownTime - elapsed;
if (remainingTime <= 0) {
this.stop(() => {
this.time = { m: '00', s: '00', ms: '000' };
if (callback) callback();
console.log('倒计时已结束');
});
} else {
this.time = this.diffTime(0, remainingTime, countdownTime);
}
}, 1);
}
reset(callback) {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
this.time = { m: '00', s: '00', ms: '000' };
if (callback) callback();
console.log('计时器已重置');
}
stop(callback) {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
if (callback) callback(this.time);
}
}
}
export default Timer;