import { ref, computed, onActivated, onDeactivated, onBeforeUnmount } from 'vue' import { raf, cancelRaf } from '..' export type CurrentTime = { days: number hours: number total: number minutes: number seconds: number milliseconds: number } export type UseCountDownOptions = { time: number millisecond?: boolean 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) } export function useCountDown(options: UseCountDownOptions) { let rafId: number let endTime: number let counting: boolean let deactivated: boolean const remain = ref(options.time) const current = computed(() => parseTime(remain.value)) const pause = () => { counting = false cancelRaf(rafId) } const getCurrentRemain = () => Math.max(endTime - Date.now(), 0) const setRemain = (value: number) => { remain.value = value options.onChange?.(current.value) if (value === 0) { pause() options.onFinish?.() } } const microTick = () => { rafId = raf(() => { if (counting) { setRemain(getCurrentRemain()) if (remain.value > 0) { microTick() } } }) } const macroTick = () => { rafId = 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) onActivated(() => { if (deactivated) { counting = true deactivated = false tick() } }) onDeactivated(() => { if (counting) { pause() deactivated = true } }) return { start, pause, reset, current } }