Vue 倒计时小组件
商城类应用开发中经常要遇到秒杀价或者到时间点开始优惠,这种业务了逻辑通常需要使用到倒计时功能。
主要使用到setTimeout方法,循环的不断调用清除调用清除,具体代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | import { cancelRaf, rAF } from '@/utils/raf' import { ref, computed, type Ref } from 'vue' //定义一个时间类型 type currentTime = { days: number hours: number minutes: number seconds: number millsecond: number total: number } type UseCountDownOptions = { time: number millisecond?: boolean onchenge?: (current: currentTime) => void finish?: () => void } const SECOND = 1000 const MINUTE = SECOND * 60 const HOURS = 60 * MINUTE const DAY = 24 * HOURS const parseTime = (time: number) => { const days = Math.floor(time / DAY) const hours = Math.floor((time % DAY) / HOURS) const minutes = Math.floor((time % HOURS) / MINUTE) const seconds = Math.floor((time % MINUTE) / SECOND) const millsecond = Math.floor(time % SECOND) return { days, hours, minutes, seconds, millsecond, total: time, } } const isSameSecond = (time1: number, time2: number) => { return Math.floor(time1 / SECOND) === Math.floor(time2 / SECOND) } export function UseCountDown(optoin: UseCountDownOptions) { let refId: number let countting: boolean let endTme: number //倒计时剩余时间 const remain: Ref = ref(optoin.time) const current = computed(() => parseTime(remain.value)) const pause = () => { countting = false optoin.finish?.() } const getCurrentRemain = () => Math.max(endTme - Date.now(), 0) const resetRemain = (value: number) => { remain.value = Math.max(endTme - Date.now(), 0) optoin.onchenge?.(current.value) if (value === 0) { pause() cancelRaf(refId) } } //毫秒级倒计时循环 const miroTick = () => { refId = rAF(() => { if (countting) { resetRemain(getCurrentRemain()) if (remain.value > 0) { miroTick() } } }) } //秒级倒计时循环 const maroTick = () => { refId = rAF(() => { if (countting) { const cRemain = getCurrentRemain() if (!isSameSecond(cRemain, remain.value) || cRemain === 0) { resetRemain(cRemain) } if (remain.value > 0) { maroTick() } } }) } const start = () => { if (!countting) { countting = true endTme = optoin.time + Date.now() if (optoin.millisecond) { miroTick() } else { maroTick() } } } const reset = (totalTime = optoin.time) => { pause() remain.value = totalTime } return { start, pause, reset, current, } } |
上面的rAf实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | export const rAF = requestAnimationFrame || function (callback) { setTimeout(callback, 1000 / 60) } // requestAnimationFrame 屏幕刷新函数,每秒钟刷新60次,版本太低的浏览器没有这个函数,使用 1000 / 60 timout 模拟 export const cancelRaf = cancelAnimationFrame || function (id: number) { clearTimeout(id) } export const doubleRaf = (fn: () => void) => { rAF(() => { rAF(fn) }) } |
使用举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <script setup lang= "ts" > import type { ICountdown } from '@/types' import { UseCountDown } from '@/use/useCountDown' interface IProps { data: ICountdown } const props = defineProps<IProps>() const countDown = UseCountDown({ time: props.data.time }) // 开始计时 countDown.start() const current = countDown.current const padStart = (num: number) => { return num.toString().padStart(2, '0' ) } </script> <template> <div class = "home-countdown" > <div class = "home-countdown__info" > <span class = "number" >{{ padStart(current.hours) }}</span> <span class = "colon" >:</span> <span class = "number" >{{ padStart(current.minutes) }}</span> <span class = "colon" >:</span> <span class = "number" >{{ padStart(current.seconds) }}</span> </div> </div> </template> .home-countdown { border-radius: 8px; width: 180px; height: 180px; background: linear-gradient(to bottom, rgb(252, 202, 202), white, white, white); padding: 15px 10px; box-sizing: border-box; justify-content: end; &__info { margin: 0 auto; text-align: center; display: flex; align-items: center; .number { font-size: 12px; background: rgb(252, 78, 78); color: white; padding: 2px; border-radius: 2px; width: 20px; font-weight: bold; } .colon { margin: 0 1px; color: red; } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端