基于Vue3創(chuàng)建一個簡單的倒計時組件
需要從父級獲取的數(shù)據(jù)
time
: 當(dāng)前倒計時的剩余時間,傳秒或毫秒isMilliSecond
: 用來判斷當(dāng)前的傳入值是秒還是毫秒值end
: 用來傳入具體的終點(diǎn)時間,傳入秒級時間戳或毫秒級時間戳format
: 用來控制最終的顯示格式,默認(rèn)格式'D天HH時MM分SS秒'
flag
: 用來判斷,是否在最高值為0時,不顯示最高值
// countDown.vue <script setup lang="ts"> const props = defineProps({ time: { type: [Number, String], default: 0, }, isMilliSecond: { type: Boolean, default: false, }, end: { type: [Number, String], default: 0, }, format: { type: String, default: () => 'D天HH時MM分SS秒', }, flag: { type: Boolean, default: false, } }) </script> <template> <div class="count_down"> {{ timeStr }} </div> </template>
基礎(chǔ)變量
curTime
: 存儲當(dāng)前時間,因?yàn)楫?dāng)瀏覽器退至后臺時,會將setTimeout
等定時任務(wù)暫停,通過curTime
用以更新倒計時days
,hours
,mins
,seconds
: 倒計時的各個部分<想著總不能超過一年倒計時吧>timer
: 存儲定時器remainingTime
: 計算倒計時的秒數(shù)timeStr
: 格式化時間字符串
// countDown.vue <script setup lang="ts"> import { computed, onMounted, ref, watch, type Ref } from 'vue'; const props = defineProps({ time: { type: [Number, String], default: 0, }, isMilliSecond: { type: Boolean, default: false, }, end: { type: [Number, String], default: 0, }, format: { type: String, default: () => 'D天HH時MM分SS秒', }, flag: { type: Boolean, default: false, } }) let curTime = 0 const days: Ref<string | number> = ref('0') const hours: Ref<string | number> = ref('00') const mins: Ref<string | number> = ref('00') const seconds: Ref<string | number> = ref('00') let timer: any = null; const remainingTime = computed(() => { if(props.end) { let end = props.isMilliSecond ? +props.end : +props.end * 1000; end -= Date.now(); return Math.round(end / 1000); } const time = props.isMilliSecond ? Math.round(+props.time / 1000) : Math.round(+props.time) return time }) const timeStr = computed(() => { const o: { [key: string]: any } = { 'D+': days.value, 'H+': hours.value, 'M+': mins.value, 'S+': seconds.value, } let str = props.format; // 當(dāng)最高值為0時,去除值及其單位,有缺陷,只能去除對應(yīng)目標(biāo)前的所有字段 if(days.value == 0 && props.flag) { let regexPattern = /.*(?=H)/; if(hours.value == 0) { regexPattern = /.*(?=M)/; if(mins.value == 0) { regexPattern = /.*(?=S)/; } } str = str.replace(regexPattern, ''); } for (var k in o) { // 括號的目的是將占位符的模式 k 捕獲到一個分組中,以便在替換字符串中的占位符時能夠引用它。 str = str.replace(new RegExp(`(${k})`, 'g'), function(match, group) { let time = group.length === 1 ? o[k] : `00${o[k]}`.slice(-group.length); // 如果是天數(shù),不管是什么格式,都把天數(shù)顯示完整,但如果多個D,會在小于10之前加0 if(k == 'D+' && group.length > 1) { time = o[k]; if(time < 10) { time = `0${time}` } } return time }); } return str; }) </script> <template> <div class="count_down"> {{ timeStr }} </div> </template>
基礎(chǔ)方法
countDown
: 進(jìn)入頁面后立即執(zhí)行countDown
,并執(zhí)行countdown
,從而開始倒計時formatTime
: 將remainingTime
轉(zhuǎn)化成天數(shù),小時,分鐘,秒數(shù)的方法countdown
: 獲取時間后開始倒計時的執(zhí)行,
// countDown.vue <script setup lang="ts"> const countDown = () => { curTime = Date.now() countdown(remainingTime.value) } const formatTime = (time: number) => { const secondsInMinute = 60; const secondsInHour = 24; let t = time; let ss = t % secondsInMinute; t = (t - ss) / secondsInMinute; const mm = t % secondsInMinute; t = (t - mm) / secondsInMinute; const hh = t % secondsInHour; t = (t - hh) / secondsInHour; const dd = t % secondsInHour; return { dd, hh, mm, ss }; } const countdown = (time: number) => { timer && clearTimeout(timer) if(time < 0) { return; } const { dd, hh, mm, ss } = formatTime(time); days.value = dd || 0; hours.value = hh || 0; mins.value = mm || 0; seconds.value = ss || 0; timer = setTimeout(() => { const now = Date.now(); const diffTime = Math.floor((now - curTime) / 1000) const step = diffTime > 1 ? diffTime : 1; // 頁面退到后臺的時候不會計時,對比時間差,大于1s的重置倒計時 curTime = now; countdown(time - step); }, 1000); } onMounted(() => { countDown(); }) </script>
為什么不使用setInterval來實(shí)現(xiàn)
- 間隔不準(zhǔn)確:
setInterval
的間隔并不保證準(zhǔn)確,因?yàn)樗皇菍⒒卣{(diào)函數(shù)添加到消息隊列,實(shí)際執(zhí)行時間依賴于主線程的負(fù)載和事件循環(huán),可能會被跳過或累積多次執(zhí)行。 - 堆積問題: 如果一個
setInterval
回調(diào)執(zhí)行的時間比其間隔短,那么它會疊加執(zhí)行。這可能會導(dǎo)致不必要的資源消耗和不符合設(shè)計預(yù)期的行為。
這些問題通常是由于 JavaScript
的單線程執(zhí)行和事件循環(huán)機(jī)制導(dǎo)致的。在實(shí)際開發(fā)中,為了更準(zhǔn)確地處理定時任務(wù),通常會使用 setTimeout
和遞歸或計算屬性來處理定時任務(wù)。 雖然 setInterval
有一些局限性,但在某些情況下它仍然可以派上用場,特別是對于一些簡單的定時操作。但在需要更精確的定時和依賴于前后狀態(tài)的場景中,通常會選擇使用 setTimeout
或其他更高級的定時管理方法。
完整代碼
// countDown.vue <script setup lang="ts"> import { computed, onMounted, ref, watch, type Ref } from 'vue'; const props = defineProps({ time: { type: [Number, String], default: 0, }, isMilliSecond: { type: Boolean, default: false, }, end: { type: [Number, String], default: 0, }, format: { type: String, default: () => 'D天HH時MM分SS秒', }, flag: { type: Boolean, default: false, } }) let curTime = 0 const days: Ref<string | number> = ref('0') const hours: Ref<string | number> = ref('00') const mins: Ref<string | number> = ref('00') const seconds: Ref<string | number> = ref('00') let timer: any = null; const remainingTime = computed(() => { if(props.end) { let end = props.isMilliSecond ? +props.end : +props.end * 1000; end -= Date.now(); return Math.round(end / 1000); } const time = props.isMilliSecond ? Math.round(+props.time / 1000) : Math.round(+props.time) return time }) const timeStr = computed(() => { const o: { [key: string]: any } = { 'D+': days.value, 'H+': hours.value, 'M+': mins.value, 'S+': seconds.value, } let str = props.format; // 如果天數(shù)為0的情況,希望去掉H之前的部分 if(days.value == 0 && props.flag) { let regexPattern = /.*(?=H)/; if(hours.value == 0) { regexPattern = /.*(?=M)/; if(mins.value == 0) { regexPattern = /.*(?=S)/; } } str = str.replace(regexPattern, ''); } for (var k in o) { // 括號的目的是將占位符的模式 k 捕獲到一個分組中,以便在替換字符串中的占位符時能夠引用它。 str = str.replace(new RegExp(`(${k})`, 'g'), function(match, group) { let time = group.length === 1 ? o[k] : `00${o[k]}`.slice(-group.length); if(k == 'D+' && group.length > 1) { time = o[k]; if(time < 10) { time = `0${time}` } } return time }); } return str; }) const countDown = () => { curTime = Date.now() countdown(remainingTime.value) } const formatTime = (time: number) => { const secondsInMinute = 60; const secondsInHour = 24; let t = time; let ss = t % secondsInMinute; t = (t - ss) / secondsInMinute; const mm = t % secondsInMinute; t = (t - mm) / secondsInMinute; const hh = t % secondsInHour; t = (t - hh) / secondsInHour; const dd = t % secondsInHour; return { dd, hh, mm, ss }; } const countdown = (time: number) => { timer && clearTimeout(timer) if(time < 0) { return; } const { dd, hh, mm, ss } = formatTime(time); days.value = dd || 0; hours.value = hh || 0; mins.value = mm || 0; seconds.value = ss || 0; timer = setTimeout(() => { const now = Date.now(); const diffTime = Math.floor((now - curTime) / 1000) const step = diffTime > 1 ? diffTime : 1; // 頁面退到后臺的時候不會計時,對比時間差,大于1s的重置倒計時 curTime = now; countdown(time - step); }, 1000); } watch(remainingTime, () => { countDown() }, { immediate: true }) onMounted(() => { countDown(); }) </script> <template> <div class="count_down"> {{ timeStr }} </div> </template>
// 父級調(diào)用 <script setup lang="ts"> import countDown from './components/countDown.vue'; </script> <template> <div id="app"> <count-down :end="1698980400000" :is-milli-second="true" :flag="true" /> </div> </template>
弊端
雖然這樣能夠通過父級傳入的格式進(jìn)行對應(yīng)的顯示,但是這樣的同時,無法對每個單元的內(nèi)容或者樣式進(jìn)行調(diào)整,也無法根據(jù)父級來動態(tài)顯示不同的樣式 想法: 可以通過插槽的方式,將值傳遞給父級,通過父級來控制顯示的內(nèi)容
調(diào)整之后的代碼:基本代碼無調(diào)整,通過插槽將值 會傳給父級
// countDown.vue <script setup lang="ts"> import { computed, onMounted, ref, watch, type Ref } from 'vue'; const props = defineProps({ time: { type: [Number, String], default: 0, }, isMilliSecond: { type: Boolean, default: false, }, end: { type: [Number, String], default: 0, }, }) let curTime = 0 const days: Ref<string | number> = ref('0') const hours: Ref<string | number> = ref('00') const mins: Ref<string | number> = ref('00') const seconds: Ref<string | number> = ref('00') let timer: any = null; const remainingTime = computed(() => { if(props.end) { let end = props.isMilliSecond ? +props.end : +props.end * 1000; end -= Date.now(); return Math.round(end / 1000); } const time = props.isMilliSecond ? Math.round(+props.time / 1000) : Math.round(+props.time) return time }) const countDown = () => { curTime = Date.now() countdown(remainingTime.value) } const formatTime = (time: number) => { const secondsInMinute = 60; const secondsInHour = 24; let t = time; let ss = t % secondsInMinute; t = (t - ss) / secondsInMinute; const mm = t % secondsInMinute; t = (t - mm) / secondsInMinute; const hh = t % secondsInHour; t = (t - hh) / secondsInHour; const dd = t % secondsInHour; return { dd, hh, mm, ss }; } const countdown = (time: number) => { timer && clearTimeout(timer) if(time < 0) { return; } const { dd, hh, mm, ss } = formatTime(time); days.value = dd || 0; hours.value = hh || 0; mins.value = mm || 0; seconds.value = ss || 0; timer = setTimeout(() => { const now = Date.now(); const diffTime = Math.floor((now - curTime) / 1000) const step = diffTime > 1 ? diffTime : 1; // 頁面退到后臺的時候不會計時,對比時間差,大于1s的重置倒計時 curTime = now; countdown(time - step); }, 1000); } watch(remainingTime, () => { countDown() }, { immediate: true }) onMounted(() => { countDown(); }) </script> <template> <div class="count_down"> <slot v-bind="{ d: days, h: hours, m: mins, s: seconds, dd: `00${days}`.slice(-2), hh: `00${hours}`.slice(-2), mm: `00${mins}`.slice(-2), ss: `00${seconds}`.slice(-2), }"></slot> </div> </template>
// 父級調(diào)用 <script setup lang="ts"> import countDown from './components/countDown.vue'; </script> <template> <div id="app"> <count-down v-slot="timeObj" :end="1698980400000" :is-milli-second="true"> {{timeObj.d}}天{{timeObj.hh}}小時{{timeObj.mm}}分鐘{{timeObj.ss}}秒 </count-down> </div> </template>
以上就是基于Vue3創(chuàng)建一個簡單的倒計時組件的詳細(xì)內(nèi)容,更多關(guān)于Vue3倒計時組件的資料請關(guān)注腳本之家其它相關(guān)文章!
- Vue3實(shí)現(xiàn)獲取驗(yàn)證碼按鈕倒計時效果
- Vue3動態(tài)倒計時的代碼實(shí)現(xiàn)
- Vue3+Hooks實(shí)現(xiàn)4位隨機(jī)數(shù)和60秒倒計時的示例代碼
- vue3實(shí)現(xiàn)封裝時間計算-日期倒計時組件-還有XX天&第XX天
- vue3發(fā)送驗(yàn)證碼倒計時功能的實(shí)現(xiàn)(防止連點(diǎn)、封裝復(fù)用)
- Vue3?實(shí)現(xiàn)驗(yàn)證碼倒計時功能
- Vue3?實(shí)現(xiàn)驗(yàn)證碼倒計時功能(刷新保持狀態(tài))
- 使用Vue3實(shí)現(xiàn)倒計時器及倒計時任務(wù)的完整代碼
相關(guān)文章
Vue表單校驗(yàn)validate和validateField的使用及區(qū)別詳解
validateField?和?validate?都可以用于表單驗(yàn)證,但是它們的作用有所不同,下面這篇文章主要給大家介紹了關(guān)于Vue表單校驗(yàn)validate和validateField的使用及區(qū)別的相關(guān)資料,需要的朋友可以參考下2024-04-04vue3?+?elementPlus?reset重置表單問題
這篇文章主要介紹了vue3?+?elementPlus?reset重置表單問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05vue3?實(shí)現(xiàn)關(guān)于?el-table?表格組件的封裝及調(diào)用方法
這篇文章主要介紹了vue3?實(shí)現(xiàn)關(guān)于?el-table?表格組件的封裝及調(diào)用方法,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-06-06Vue實(shí)現(xiàn)tab導(dǎo)航欄并支持左右滑動功能
本文給大家介紹利用Vue實(shí)現(xiàn)tab導(dǎo)航欄,并且通過flex布局實(shí)現(xiàn)左右滑動效果,通過代碼給大家分享tab導(dǎo)航欄布局的實(shí)現(xiàn),本文給大家展示了完整代碼,需要的朋友參考下吧2021-06-06詳解win7 cmd執(zhí)行vue不是內(nèi)部命令的解決方法
這篇文章主要介紹了詳解win7 cmd執(zhí)行vue不是內(nèi)部命令的解決方法的相關(guān)資料,這里提供了解決問題的詳細(xì)步驟,具有一定的參考價值,需要的朋友可以參考下2017-07-07如何使用Gitee Pages服務(wù) 搭建Vue項(xiàng)目
這篇文章主要介紹了如何使用Gitee Pages服務(wù) 搭建Vue項(xiàng)目,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10