Vue 倒计时小组件

发布时间 2023-12-29 14:26:13作者: 不停奔跑的蜗牛

商城类应用开发中经常要遇到秒杀价或者到时间点开始优惠,这种业务了逻辑通常需要使用到倒计时功能。

 主要使用到setTimeout方法,循环的不断调用清除调用清除,具体代码实现

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实现如下

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

使用举例

<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