Vue組件實現(xiàn)卡片動畫倒計時示例詳解
前言
最近有朋友在做投票的項目,里面有用到一個倒計時的組件,還想要個動畫效果。cv大法浸染多年的我,首先想到的是直接找個現(xiàn)有的組件。
通過一通搜索,看上的只有一個 vue2-flip-countdown,但是當我要修改大小和顏色的時候發(fā)現(xiàn)改不了,從而直接把源碼拉到項目里面,改起來也挺麻煩。
而且,在搜索大法運行幾個周天以后,其實心理已經有了一個倒計時開發(fā)整體思路,便決定自己封裝一個倒計時出來,最終實現(xiàn)效果如下:
需求拆解
- 開發(fā)一個開箱即用的組件,實現(xiàn)倒計時的效果,顯示模式為 01日01時01分01秒。
- 以終止時間為入參,計算倒計時顯示的各個數(shù)據(jù)。
- 方便的控制顯示的文案,以及主題顏色,大小。
組件設計思路
- 組件分為兩個部分
- animate-clock,控制組件數(shù)據(jù)結構,例如:自定義文案、將中文時間單位改成英文,是否換行等等
- animate-card,動畫卡片,即組件里面的具體數(shù)字部分,并加上動畫。
- clock 組件中,對傳入的 props 進行處理,最核心的為 終止時間(terminalTime),根據(jù)終止時間計算天、時、分、秒。
- 通過定時任務,把計算出來的 天、時、分、秒 數(shù)據(jù)傳入 card 組件,觸發(fā)視圖更新。
- card 組件中,當數(shù)據(jù)發(fā)生改變的時候觸發(fā)動畫效果。
- 【升級】在此基礎上進行擴展,變?yōu)橐粋€倒計時通用解決方案:
- 反向倒計時
- 只有數(shù)字的倒計時
- 只有分、秒的倒計時
- 中文、英文字符串倒計時
- 動畫抽獎
- 專注時間計時器
- ...
具體開發(fā)
animate-clock.vue
首先設計視圖部分:
<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>
很簡單的結構,現(xiàn)在版本為截圖所示的一行結構,可以看到,完全可以通過業(yè)務組件中通過傳入 props 的形式,修改每一個部分的文案,而且 樣式也可以隨時控制。
js 部分主要做這么幾件事:
- 接收 props,并聲明 data
- 聲明一個 工具函數(shù),用來 處理 小于 10 的數(shù)字,前面增加 0
- 聲明主要業(yè)務函數(shù),被定時任務調用的更新數(shù)據(jù)方法
<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() // 箭頭函數(shù)不修改當前作用域下的 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) }, /** * 轉化數(shù)字為數(shù)組,并在 頭部填充 0 * @params num: numnber * @result string[] */ toStringAndUnshiftZero(num) { const val = num.toString().split('') if (num < 10) { val.unshift('0') } return val }, }, } </script>
這一塊根本沒有什么技術含量,主要是異常數(shù)據(jù)的處理,和停止邏輯。 其實計時器清零理論上是不需要出現(xiàn)的,但是在測試過程中發(fā)現(xiàn),會出現(xiàn)最后一幀為 01 的情況,就直接清零了。
animate-card
這個組件一開始考慮的很復雜,想著監(jiān)聽數(shù)據(jù)的變化,觸發(fā)一個動畫的方法,然后這個方法支持重寫,提高組件的擴展性,但是時間不允許,后面又覺得不太必要。
最后選擇的方案很簡單,卻提供了一個可以實現(xiàn) css 能實現(xiàn)的所有效果的思路。
主要思路是通過 vue 的 transition-group 機制,將 0-9 所有的卡片都渲染好,隱藏起來,通過 v-show 來觸發(fā)綁定在 transition-group 上的動畫效果,從而實現(xiàn)動態(tài)監(jiān)聽數(shù)據(jù)變化的效果。
需要注意的是因為宿主項目中 引入了 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>
看完代碼以后很容易發(fā)現(xiàn),我設計的樣式其實一點都不好看,可以對 card-item 寫一些前端比較炫的效果。而且動畫效果也可以自定義。
項目中使用
粘貼上面兩個 vue 文件
在業(yè)務頁面中 引入并使用
使用方法如下:
<div class="vote-clock"> <animate-clock :terminalTime="'2023-07-11 23:27:00'" /> </div>
CV大法只支持 terminalTime 這一個入參。
后記
這個組件很簡單,但是也有很多可以琢磨的地方,更重要的是整理出一個開發(fā)通用組件的思路。
另外,這套代碼其實只是一個基礎結構,擴展性還是很強的,尤其是前面設計思路中第5條中提到的其他功能,在此基礎上可以很快速的進行開發(fā),感興趣的道友可以簡單琢磨一下。
大家在工作中不可避免的會遇到多種多樣的需要復用的代碼塊,有的道友會選擇提出為一個公共組件或者公共代碼塊,有的道友則選擇使用CV大法直接復用,也有的可能會另辟蹊徑在第一個場景的基礎上優(yōu)化為第二套代碼。
我以為這三種情況其實就是一個遞進的關系,首先CV大法好,發(fā)功后發(fā)現(xiàn)場景不是完全一致,進行些許優(yōu)化,進而搞出一個兼容多個可能存在的場景的通用解決方案。
在設計一個組件的時候,需要盡可能的考慮多種場景,并考慮一下后續(xù)的升級方案。想兼容所有的場景肯定不可能,那么就要知道當前組件的邊界在哪里。所以,在設計組件的時候不能只著眼于當前業(yè)務,更要考慮到其他場景。最簡單的例如:一個Button組件,除了保證全局的按鈕風格一致的前提下,也要考慮到按鈕禁用狀態(tài),大按鈕,帶圖標按鈕,按鈕組這些方面的東西。
以上就是我關于做這個小組件之后進行的一些淺顯的思考,共勉之,更多關于Vue卡片動畫倒計時的資料請關注腳本之家其它相關文章!
相關文章
詳解基于 axios 的 Vue 項目 http 請求優(yōu)化
這篇文章主要介紹了詳解基于 axios 的 Vue 項目 http 請求優(yōu)化,非常具有實用價值,需要的朋友可以參考下2017-09-09