Vue3毫秒倒计时(Countdown)

66 篇文章 3 订阅
64 篇文章 2 订阅

可自定义设置以下属性:

  • 倒计时标题(title),类型 string | v-slot,默认 ''

  • 倒计时数值(value),类型 number,单位ms,支持设置未来某时刻的时间戳 或 相对剩余时间戳,默认 undefined

  • 是否为未来某时刻(future);为 false 表示相对剩余时间戳,类型:boolean,默认 true

  • 格式化倒计时展示(format),类型 string,(Y:年,M:月,D:日,H:时,m:分钟,s:秒,S:毫秒),默认 'HH:mm:ss'

  • 倒计时数值的前缀(prefix),类型 string | v-slot,默认 ''

  • 倒计时数值的后缀(suffix),类型 string | v-slot,默认 ''

  • 设置标题的样式(titleStyle),类型:CSSProperties,默认 {}

  • 设置数值的样式(valueStyle),类型:CSSProperties,默认 {}

  • 完成后的展示文本(finishedText),类型 string | v-slot,默认 'Finished'

效果如下图:在线预览

注:组件引用方法 import { requestAnimationFrame } from '../index' 请参考以下博客: 

使用requestAnimationFrame模拟实现setTimeout和setInterval_theMuseCatcher的博客-CSDN博客使用requestAnimationFrame模拟实现setTimeout和setInterval!https://blog.csdn.net/Dandrose/article/details/130167061

①创建倒计时组件Countdown.vue:

<script setup lang="ts">
import { ref, computed, watchEffect, useSlots } from 'vue'
import type { CSSProperties } from 'vue'
import { requestAnimationFrame } from '../index'
interface Props {
  title?: string // 倒计时标题 string | v-slot
  value: number // 倒计时数值,支持设置未来某时刻的时间戳(ms) 或 相对剩余时间戳(ms)
  future?: boolean // 是否为未来某时刻;为 false 表示相对剩余时间戳
  format?: string // 格式化倒计时展示,(Y:年,M:月,D:日,H:时,m:分钟,s:秒,SSS:毫秒)
  prefix?: string // 倒计时数值的前缀 string | v-slot
  suffix?: string // 倒计时数值的后缀 string | v-slot
  titleStyle?: CSSProperties // 设置标题的样式
  valueStyle?: CSSProperties // 设置数值的样式
  finishedText?: string // 完成后的展示文本 string | v-slot
}
const props = withDefaults(defineProps<Props>(), { // 基于类型的声明
  title: 'Countdown',
  value: undefined,
  future: true,
  format: 'HH:mm:ss',
  prefix: '',
  suffix: '',
  titleStyle: () => ({}),
  valueStyle: () => ({}),
  finishedText: 'Finished'
})
const slots = useSlots()
const showPrefix = computed(() => {
  const prefixSlots = slots.prefix?.()
  if (prefixSlots) {
    return Boolean(prefixSlots[0].children !== 'v-if' && prefixSlots?.length)
  }
  return props.prefix
})
const showSuffix = computed(() => {
  const suffixSlots = slots.suffix?.()
  if (suffixSlots) {
    return Boolean(suffixSlots[0].children !== 'v-if' && suffixSlots?.length)
  }
  return props.suffix
})
const futureTime = ref(0) // 未来截止时间戳
const restTime = ref() // 剩余时间戳
const showType = computed(() => {
  return {
    showMillisecond: props.format.includes('SSS'),
    showYear: props.format.includes('Y'),
    showMonth: props.format.includes('M'),
    showDay: props.format.includes('D'),
    showHour: props.format.includes('H'),
    showMinute: props.format.includes('m'),
    showSecond: props.format.includes('s')
  }
})
function fixedTwo (value: number): string {
  return value < 10 ? '0' + value : String(value)
}
function timeFormat (time: number|null): string {
  if (time === null) {
    return '--'
  }
  let showTime = props.format
  if (showType.value.showMillisecond) {
    var S = time % 1000
    showTime = showTime.replace('SSS', '0'.repeat(3 - String(S).length) + S)
  }
  time = Math.floor(time / 1000) // 将时间转为s为单位
  if (showType.value.showYear) {
    var Y = Math.floor(time / (60 * 60 * 24 * 30 * 12))
    showTime = showTime.includes('YY') ? showTime.replace('YY', fixedTwo(Y)) : showTime.replace('Y', String(Y))
  } else {
    var Y = 0
  }
  if (showType.value.showMonth) {
    time = time - Y * 60 * 60 * 24 * 30 * 12
    var M = Math.floor(time / (60 * 60 * 24 * 30))
    showTime = showTime.includes('MM') ? showTime.replace('MM', fixedTwo(M)) : showTime.replace('M', String(M))
  } else {
    var M = 0
  }
  if (showType.value.showDay) {
    time = time - M * 60 * 60 * 24 * 30
    var D = Math.floor(time / (60 * 60 * 24))
    showTime = showTime.includes('DD') ? showTime.replace('DD', fixedTwo(D)) : showTime.replace('D', String(D))
  } else {
    var D = 0
  }
  if (showType.value.showHour) {
    time = time - D * 60 * 60 * 24
    var H = Math.floor(time / (60 * 60))
    showTime = showTime.includes('HH') ? showTime.replace('HH', fixedTwo(H)) : showTime.replace('H', String(H))
  } else {
    var H = 0
  }
  if (showType.value.showMinute) {
    time = time - H * 60 * 60
    var m = Math.floor(time / 60)
    showTime = showTime.includes('mm') ? showTime.replace('mm', fixedTwo(m)) : showTime.replace('m', String(m))
  } else {
    var m = 0
  }
  if (showType.value.showSecond) {
    var s = time - m * 60
    showTime = showTime.includes('ss') ? showTime.replace('ss', fixedTwo(s)) : showTime.replace('s', String(s))
  }
  return showTime
}
const emit = defineEmits(['finish'])
function CountDown () {
  if (futureTime.value > Date.now()) {
    restTime.value = futureTime.value - Date.now()
    requestAnimationFrame(CountDown)
  } else {
    restTime.value = 0
    emit('finish')
  }
}
watchEffect(() => {
  // 只有数值类型的值,且是有穷的(finite),才返回 true
  if (Number.isFinite(props.value)) { // 检测传入的参数是否是一个有穷数
    if (props.future) { // 未来某时刻的时间戳,单位ms
      if (props.value >= Date.now()) {
        futureTime.value = props.value
      }
    } else { // 相对剩余时间,单位ms
      if (props.value >= 0) {
        futureTime.value = (props.value as number) + Date.now()
      }
    }
    requestAnimationFrame(CountDown)
  } else {
    restTime.value = null
  }
})
</script>
<template>
  <div class="m-countdown">
    <div class="u-title" :style="titleStyle">
      <slot name="title">{{ props.title }}</slot>
    </div>
    <div class="m-time">
      <template v-if="showPrefix">
        <span class="u-prefix" v-if="showPrefix || restTime > 0 || restTime === null">
          <slot name="prefix">{{ prefix }}</slot>
        </span>
      </template>
      <span class="u-time-value" :style="valueStyle" v-if="finishedText && restTime === 0 && restTime !== null">
        <slot name="finish">{{ finishedText }}</slot>
      </span>
      <span class="u-time-value" :style="valueStyle" v-if="Number.isFinite(restTime) && restTime > 0">{{ timeFormat(restTime) }}</span>
      <template v-if="showSuffix">
        <span class="u-suffix" v-if="showSuffix || restTime > 0 || restTime === null">
          <slot name="suffix">{{ suffix }}</slot>
        </span>
      </template>
    </div>
  </div>
</template>
<style lang="less" scoped>
.m-countdown {
  display: inline-block;
  line-height: 1.5714285714285714;
  .u-title {
    margin-bottom: 4px;
    color: rgba(0, 0, 0, .45);
    font-size: 14px;
  }
  .m-time {
    color: rgba(0, 0, 0, .88);
    font-size: 24px;
    font-family: 'Helvetica Neue'; // 保证数字等宽显示
    .u-prefix {
      display: inline-block;
      margin-inline-end: 4px;
    }
    .u-time-value {
      display: inline-block;
      direction: ltr;
    }
    .u-suffix {
      display: inline-block;
      margin-inline-start: 4px;
    }
  }
}
</style>

②在要使用的页面引入:

<script setup lang="ts">
import Countdown from './Countdown.vue'
function onFinish () {
  console.log('countdown finished')
}
</script>
<template>
  <div>
    <h1>Countdown 倒计时</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <h3 class="mb10">format: MM月 DD天 HH:mm:ss</h3>
    <Countdown
      title="Countdown 1年"
      :value="12 * 30 * 24 * 60 * 60 * 1000"
      :future="false"
      format="MM月 DD天 HH:mm:ss"
      finished-text="Finished"
      @finish="onFinish" />
    <h2 class="mt30 mb10">毫秒倒计时</h2>
    <h3 class="mb10">format: Y 年 M 月 D 天 H 时 m 分 s 秒 SSS 毫秒</h3>
    <Countdown
      title="Million Seconds"
      :value="12 * 30 * 24 * 60 * 60 * 1000"
      :future="false"
      format="Y 年 M 月 D 天 H 时 m 分 s 秒 SSS 毫秒"
      finished-text="Finished"
      @finish="onFinish" />
    <h2 class="mt30 mb10">使用插槽</h2>
    <Countdown
      :value="2471875200000"
      format="Y 年 M 月 D 天 H 时 m 分 s 秒 SSS 毫秒"
      finished-text="Finished"
      @finish="onFinish">
      <template #title>2048年 五一 Countdown</template>
      <template #prefix>There's only </template>
      <template #suffix> left for the end.</template>
    </CountDown>
    <h2 class="mt30 mb10">自定义样式</h2>
    <Countdown
      :value="2485094400000"
      format="Y 年 MM 月 DD 天 HH 时 mm 分 ss 秒 SSS 毫秒"
      :title-style="{fontWeight: 500, fontSize: '18px'}"
      :value-style="{fontWeight: 600, color: '#1677ff'}"
      @finish="onFinish">
      <template #title>2048年 十一 Countdown</template>
    </CountDown>
    <h2 class="mt30 mb10">倒计时已完成</h2>
    <Countdown :value="0" :future="false" @finish="onFinish" />
  </div>
</template>
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值