需求
- 开发一个开箱即用的组件,实现倒计时的效果,显示模式为 01日01时01分01秒。
- 以终止时间为入参,计算倒计时显示的各个数据。
- 方便的控制显示的文案,以及主题颜色,大小。
组件思路
- 组件分为两个部分
animate-clock,控制组件数据结构,例如:自定义文案、将中文时间单位改成英文,是否换行等等
animate-card,动画卡片,即组件里面的具体数字部分,并加上动画。
- clock 组件中,对传入的 props 进行处理,最核心的为
终止时间(terminalTime)
,根据终止时间计算天、时、分、秒
。 - 通过定时任务,把计算出来的 天、时、分、秒 数据传入 card 组件,触发视图更新。
- card 组件中,当数据发生改变的时候触发动画效果。
- 【升级】在此基础上进行扩展,变为一个倒计时通用解决方案:
反向倒计时
只有数字的倒计时
只有分、秒的倒计时
中文、英文字符串倒计时
动画抽奖
专注时间计时器
...
开发
首先设计视图部分:
<template>
<div class="animate-clock">
<!-- <p>{{days}}{{hours}}{{minites}}{{seconds}}</p> -->
<span>距离结束还剩</span>
<animate-card :val="days" :size="16" :self-disabled="disabled" />
<span>天</span>
<animate-card :val="hours" :size="16" :self-disabled="disabled" />
<span>时</span>
<animate-card :val="minites" :size="16" :self-disabled="disabled" />
<span>分</span>
<animate-card :val="seconds" :size="16" :self-disabled="disabled" />
<span>秒</span>
</div>
</template>
<style lang="scss" scoped>
.animate-clock {
width: 100%;
text-align: center;
font-size: 16px;
font-weight: bold;
padding: 40px 0 ;
}
</style>
很简单的结构,现在版本为截图所示的一行结构,可以看到,完全可以通过业务组件中通过传入 props 的形式,修改每一个部分的文案,而且 样式也可以随时控制。
js 部分主要做这么几件事:
1. 接收 props,并声明 data
2. 声明一个 工具函数,用来 处理 小于 10 的数字,前面增加 0
3. 声明主要业务函数,被定时任务调用的更新数据方法
<script>
import animateCard from './animate-card.vue'
export default {
components: { animateCard },
props: {
terminalTime: String,
},
data() {
return {
days: ['0', '0'],
hours: ['0', '0'],
minites: ['0', '0'],
seconds: ['0', '0'],
setIntVal: null,
disabled: false,
}
},
mounted() {
// 先调用一次
this.updateClock()
// 箭头函数不修改当前作用域下的 this 指向
this.setIntVal = setInterval(() => {
this.updateClock()
}, 1000)
},
methods: {
/**
* 更新计时器
* @result void
*/
updateClock() {
let now = new Date().getTime()
let stopTime = 0
// 错误入参 处理逻辑
try {
stopTime = new Date(this.terminalTime).getTime()
} catch (err) {
console.error(err)
return false
}
// 终止逻辑
const remainingTime = stopTime - now
if (remainingTime < 1000) {
clearInterval(this.setIntVal)
this.setIntVal = null
// 计时器 清零
this.days = this.hours = this.minites = this.seconds = ['0', '0']
this.disabled = true
console.log('时间到!')
return false
}
// 计算 日、时、分、秒
let days = parseInt(remainingTime / (24 * 60 * 60 * 1000))
let hours = parseInt(
(remainingTime - 24 * 60 * 60 * 1000 * days) / (60 * 60 * 1000)
)
let minites = parseInt(
(remainingTime - 24 * 60 * 60 * 1000 * days - 60 * 60 * 1000 * hours) /
(60 * 1000)
)
let seconds = parseInt(
(remainingTime -
24 * 60 * 60 * 1000 * days -
60 * 60 * 1000 * hours -
60 * 1000 * minites) /
1000
)
// 更新 data
this.days = this.toStringAndUnshiftZero(days)
this.hours = this.toStringAndUnshiftZero(hours)
this.minites = this.toStringAndUnshiftZero(minites)
this.seconds = this.toStringAndUnshiftZero(seconds)
},
/**
* 转化数字为数组,并在 头部填充 0
* @params num: numnber
* @result string[]
*/
toStringAndUnshiftZero(num) {
const val = num.toString().split('')
if (num < 10) {
val.unshift('0')
}
return val
},
},
}
</script>
这一块根本没有什么技术含量,主要是异常数据的处理,和停止逻辑。 其实计时器清零理论上是不需要出现的,但是在测试过程中发现,会出现最后一帧为 01 的情况,就直接清零了。
animate-card
这个组件一开始考虑的很复杂,想着监听数据的变化,触发一个动画的方法,然后这个方法支持重写,提高组件的扩展性,但是时间不允许,后面又觉得不太必要。
最后选择的方案很简单,却提供了一个可以实现 css 能实现的所有效果的思路。
主要思路是通过 vue 的 transition-group 机制,将 0-9 所有的卡片都渲染好,隐藏起来,通过 v-show 来触发绑定在 transition-group 上的动画效果,从而实现动态监听数据变化的效果。
需要注意的是因为宿主项目中 引入了 animate.css,所以就直接使用 animate 的动画效果了。
有需要的,可以翻看文档 【animate.css 官方文档】进行配置。
如果直接CV这套代码的话,没有动画效果。
代码如下:
<template>
<div class="aimate-card">
<div class="card-group" v-for="(item,idx) in val" :key="idx" :style="{'font-size': size+'px'}">
<transition-group enter-active-class="animate__animated animate__bounceIn" leave-active-class="animate__animated animate__fadeOutDown">
<div class="card-item" :class="{'disabled': selfDisabled}" v-for="num in 10" :key="num" v-show="item== num-1">{{num-1}}</div>
</transition-group>
</div>
</div>
</template>
<script>
export default {
props: {
val: {
type: Array,
default: () => ['0', '0'],
},
size: {
type: Number,
default: 16,
},
selfDisabled: {
type: Boolean,
default: false,
},
},
mounted() {
console.log(this.selfDisabled)
},
}
</script>
<style lang="scss" scoped>
.aimate-card {
width: auto;
display: inline-block;
height: 100%;
.card-group {
display: inline-block;
position: relative;
width: 40px;
padding: 5px;
height: 100%;
vertical-align: middle;
.card-item {
position: absolute;
background: #3a7fe4;
color: #fff;
width: 30px;
height: 40px;
top: -20px;
line-height: 40px;
}
.disabled {
background: #ccc !important;
}
}
}
</style>
看完代码以后很容易发现,我设计的样式其实一点都不好看,可以对 card-item 写一些前端比较炫的效果。而且动画效果也可以自定义。
项目中使用
1. 粘贴上面两个 vue 文件
2. 在业务页面中 引入并使用
使用方法如下:
<div class="vote-clock">
<animate-clock :terminalTime="'2023-07-11 23:27:00'" />
</div>