我用Vue3+TS实现了一个新年倒计时组件,适用于各种场景

·  阅读 2357
我用Vue3+TS实现了一个新年倒计时组件,适用于各种场景

PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛

大家好,我是一碗周,一个不想被喝(内卷)的前端。如果写的文章有幸可以得到你的青睐,万分有幸~

写在前面

最近在写一个考试系统,有一个倒计时自动交卷的需求,正好也马上春节,就有了编写一个倒计时组件的想法。

对于这个倒计时组件,它应该具有这样的功能:

  • 字体、颜色等样式可以由使用者自定义;
  • 结束时间也可以由使用者自定义;
  • 倒计时结束以后,倒计时组件emit一个事件,以便进行后续操作。

现在我们根据这样的需求,去编写这个组件。

组件属性和事件

首先我们创建一个Vue3+TS+setup的基础组件,代码如下:

<template>
  <div></div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
onMounted(() => {
  console.log('mounted!')
})
</script>
<style scoped></style>

复制代码

然后定义组件的props和emits,方便后续编写,定义的内容如下:

interface Props {
  label?: string
  // 字体颜色和背景颜色,接收十六进制字符串
  textColor?: string
  backgroundColor?: string
  height?: string
  width?: string
  // 依次传递 label 时间 描述 的字体大小
  fontSize?: string[]
  mFontSize?: string[]
  // 结束时间,单位毫秒
  endTime?: number
}
const emit = defineEmits(['time-end'])
const props = withDefaults(defineProps<Props>(), {
  label: '春节倒计时',
  // 默认样式
  textColor: '#d99c3b',
  backgroundColor: '#cf1322',
  width: '100%',
  height: '100%',
  fontSize: () => {
    return ['2.2rem', '1.2rem', '3rem']
  },
  mFontSize: () => {
    return ['1.7rem', '0.8rem', '2rem']
  },
  // 2022年春节的时间戳,单位毫秒
  endTime: new Date(2022, 1, 1).getTime(),
})

复制代码

这里我们使用了TS的类型约束,增加代码的健壮性。

静态效果

我们定义了内容之后,来编写一下静态效果,HTML骨架如下:

<div class="middle">
  <h1 class="label">{{ props.label }}</h1>
  <div class="time">
    <span>
      <div>20</div></span>
    <span>
      <div>12</div></span>
    <span>
      <div>20</div></span>
    <span>
      <div>40</div></span>
  </div>
</div>
复制代码

在Vue3中,我们可以在<style>标签中使用v-bind指令,具体可以参考单文件组件样式特性 | Vue.js (vuejs.org),我们的样式如下:

.middle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: v-bind('props.height');
  width: v-bind('props.width');
  text-align: center;
  user-select: none;
  background-color: v-bind('props.backgroundColor');
}
.label {
  font-size: v-bind('props.fontSize[0]');
  color: v-bind('props.textColor');
}

.time {
  color: v-bind('props.textColor');
  text-transform: uppercase;
  display: flex;
  justify-content: center;
}

.time span {
  padding: 0 14px;
  font-size: v-bind('props.fontSize[1]');
}

.time span div {
  font-size: v-bind('props.fontSize[2]');
}

@media (max-width: 740px) {
  .label {
    font-size: v-bind('props.mFontSize[0]');
  }
  .time span {
    padding: 0 16px;
    font-size: v-bind('props.mFontSize[1]');
  }
  .time span div {
    font-size: v-bind('props.mFontSize[2]');
  }
}
复制代码

它最终是下面这个样子:

image.png

获取剩余时间

现在我们有了基础的样式以及需要的属性,然后我们需要获取一下距离endTime的剩余时间,这里我们采用dayjs中提供的diff方法(因为我在项目中引入了day.js,如果没有的话,可以用两个时间戳做加法,效果是一样的),这个方法可以返回指定两个时间的差异,使用方法如下:

import dayjs from 'dayjs'
const timeLeft = ref(dayjs(props.endTime).diff(dayjs(Date.now()), 'seconds'))

复制代码

返回endTime到现在的时间,单位是秒。

然后我们定义一个计算属性,来将这个秒格式化一下,示例代码如下:

const durationFormatter = computed(() => {
  const time = timeLeft.value
  if (!time) return { ss: 0 }
  let t = time
  const ss = t % 60
  t = (t - ss) / 60
  if (t < 1) return { ss }
  const mm = t % 60
  t = (t - mm) / 60
  if (t < 1) return { mm, ss }
  const hh = t % 24
  t = (t - hh) / 24
  if (t < 1) return { hh, mm, ss }
  const dd = t
  return { dd, hh, mm, ss }
})
复制代码

获取之后我们只需要在<template>使用我们的计算属性,从而动态的显示数据。

开始倒计时

最后,我们还剩一步,就是让这个时间动起来。这里我们采用setTimeout而不是setInterVal,实现代码如下:

let timer: any = null
const getTime = () => {
  // 避免重复执行 setTimeout
  timer && clearTimeout(timer)
  timer = setTimeout(() => {
    if (timeLeft.value >= 0) {
      --timeLeft.value
      getTime() // 递归调用
    } else {
      emit('time-end', props.endTime)
    }
  }, 1000)
}
复制代码

当倒计时结束后,将time-end事件emit到父组件,并将结束时间作为event参数。

使用组件

使用这个组件也比较简单,直接引入即可,代码如下:

<template>
  <countDown width="100vw" height="100vh" @time-end="timeEnd"></countDown>
</template>
<script setup lang="ts">
import { countDown } from '@/components/countDown/index'
const timeEnd = (e: any) => {
  console.log(e)
}
</script>
<style scoped></style>

复制代码

运行效果如下:

倒计时组件.gif

写在最后

以上就是这篇文章的全部内容,如果觉着俺写的不错,可以点个赞支持下。

推荐

分类:
前端
分类:
前端
收藏成功!
已添加到「」, 点击更改