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

