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;
    }
  }
}

 具体代码地址:http://github.com/duzhaoquan/ele-h5.git

posted @   不停奔跑的蜗牛  阅读(314)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示