You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
3.6 KiB
TypeScript

import { ref, computed, onBeforeUnmount } from 'vue'
import { isDef } from '../common/util'
// 定义倒计时时间的数据结构
export type CurrentTime = {
days: number
hours: number
total: number
minutes: number
seconds: number
milliseconds: number
}
// 定义倒计时的配置项
export type UseCountDownOptions = {
time: number // 倒计时总时间,单位为毫秒
millisecond?: boolean // 是否开启毫秒级倒计时,默认为 false
onChange?: (current: CurrentTime) => void // 倒计时每次变化时的回调函数
onFinish?: () => void // 倒计时结束时的回调函数
}
// 定义常量
const SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
// 将时间转换为倒计时数据结构
function parseTime(time: number): CurrentTime {
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
}
}
// 判断两个时间是否在同一秒内
function isSameSecond(time1: number, time2: number): boolean {
return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
}
// 判断当前环境是否为 H5
const isH5 = process.env.UNI_PLATFORM === 'h5'
// 封装 requestAnimationFrame 和 setTimeout
function raf(fn: FrameRequestCallback): number {
return isH5 ? requestAnimationFrame(fn) : setTimeout(fn, 33)
}
function cancelRaf(id: number) {
if (isH5) {
cancelAnimationFrame(id)
} else {
clearTimeout(id)
}
}
// 定义 useCountDown 函数
export function useCountDown(options: UseCountDownOptions) {
let timer: number | null = null // 定时器
let endTime: number // 结束时间
let counting: boolean // 是否计时中
const remain = ref(options.time) // 剩余时间
const current = computed(() => parseTime(remain.value)) // 当前倒计时数据
// 暂停倒计时
const pause = () => {
counting = false
cancelRaf(timer!)
}
// 获取当前剩余时间
const getCurrentRemain = () => Math.max(endTime - Date.now(), 0)
// 设置剩余时间
const setRemain = (value: number) => {
remain.value = value
isDef(options.onChange) && options.onChange(current.value)
if (value === 0) {
pause()
isDef(options.onFinish) && options.onFinish()
}
}
// 每毫秒更新一次倒计时
const microTick = () => {
timer = raf(() => {
if (counting) {
setRemain(getCurrentRemain())
if (remain.value > 0) {
microTick()
}
}
})
}
// 每秒更新一次倒计时
const macroTick = () => {
timer = raf(() => {
if (counting) {
const remainRemain = getCurrentRemain()
if (!isSameSecond(remainRemain, remain.value) || remainRemain === 0) {
setRemain(remainRemain)
}
if (remain.value > 0) {
macroTick()
}
}
})
}
// 根据配置项选择更新方式
const tick = () => {
if (options.millisecond) {
microTick()
} else {
macroTick()
}
}
// 开始倒计时
const start = () => {
if (!counting) {
endTime = Date.now() + remain.value
counting = true
tick()
}
}
// 重置倒计时
const reset = (totalTime: number = options.time) => {
pause()
remain.value = totalTime
}
// 在组件卸载前暂停倒计时
onBeforeUnmount(pause)
return {
start,
pause,
reset,
current
}
}