基于Vue3创建一个简单的倒计时组件

 更新时间:2023年11月03日 08:31:52   作者:梦尘星月  
这篇文章主要给大家介绍了基于Vue3创建一个简单的倒计时组件的代码示例,文中通过代码示例介绍的非常详细,具有一定的参考价值,需要的朋友可以参考下

需要从父级获取的数据

  • time: 当前倒计时的剩余时间,传秒或毫秒
  • isMilliSecond: 用来判断当前的传入值是秒还是毫秒值
  • end: 用来传入具体的终点时间,传入秒级时间戳或毫秒级时间戳
  • format: 用来控制最终的显示格式,默认格式'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>

基础变量

  • curTime: 存储当前时间,因为当浏览器退至后台时,会将setTimeout等定时任务暂停,通过curTime用以更新倒计时
  • days, hours, mins, seconds: 倒计时的各个部分<想着总不能超过一年倒计时吧>
  • timer: 存储定时器
  • remainingTime: 计算倒计时的秒数
  • 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;

    // 当最高值为0时,去除值及其单位,有缺陷,只能去除对应目标前的所有字段
    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);

            // 如果是天数,不管是什么格式,都把天数显示完整,但如果多个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>

基础方法

  • countDown: 进入页面后立即执行countDown,并执行countdown,从而开始倒计时
  • formatTime: 将remainingTime转化成天数,小时,分钟,秒数的方法
  • countdown: 获取时间后开始倒计时的执行,
// 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来实现

  • 间隔不准确:setInterval 的间隔并不保证准确,因为它只是将回调函数添加到消息队列,实际执行时间依赖于主线程的负载和事件循环,可能会被跳过或累积多次执行。
  • 堆积问题: 如果一个setInterval回调执行的时间比其间隔短,那么它会叠加执行。这可能会导致不必要的资源消耗和不符合设计预期的行为。

这些问题通常是由于 JavaScript 的单线程执行和事件循环机制导致的。在实际开发中,为了更准确地处理定时任务,通常会使用 setTimeout 和递归或计算属性来处理定时任务。 虽然 setInterval 有一些局限性,但在某些情况下它仍然可以派上用场,特别是对于一些简单的定时操作。但在需要更精确的定时和依赖于前后状态的场景中,通常会选择使用 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;

    // 如果天数为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>
// 父级调用
<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>

弊端

虽然这样能够通过父级传入的格式进行对应的显示,但是这样的同时,无法对每个单元的内容或者样式进行调整,也无法根据父级来动态显示不同的样式 想法: 可以通过插槽的方式,将值传递给父级,通过父级来控制显示的内容

调整之后的代码:基本代码无调整,通过插槽将值 会传给父级

// 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>
// 父级调用
<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创建一个简单的倒计时组件的详细内容,更多关于Vue3倒计时组件的资料请关注脚本之家其它相关文章!

相关文章

  • vue中本地静态图片的路径应该怎么写

    vue中本地静态图片的路径应该怎么写

    这篇文章主要介绍了vue中本地静态图片的路径应该怎么写,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • vue-resourse将json数据输出实例

    vue-resourse将json数据输出实例

    这篇文章主要为大家详细介绍了vue-resourse将json数据输出实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • vue3+ts+vite中“@“路径失效的解决办法

    vue3+ts+vite中“@“路径失效的解决办法

    这篇文章主要介绍了vue3+ts+vite中“@“路径失效的解决办法,在使用vite脚手架生成项目时,解决引入路径失败的错误,可以按照本文介绍的步骤操作,需要的朋友可以参考下
    2024-11-11
  • vue中的mvvm模式讲解

    vue中的mvvm模式讲解

    今天小编就为大家分享一篇关于vue中的mvvm模式讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • vue结合echarts绘制一个支持切换的折线图实例

    vue结合echarts绘制一个支持切换的折线图实例

    这篇文章主要介绍了vue结合echarts绘制一个支持切换的折线图实例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • Vue3中引入scss文件的方法步骤

    Vue3中引入scss文件的方法步骤

    这篇文章主要给大家介绍了关于Vue3中引入scss文件的方法步骤,在实际项目中,各种样式往往有很多重复的情况,为了能够使样式的后续开发和维护更加惬意,将这些共同的代码进行命名然后调用这些变量是一个很好的选择,需要的朋友可以参考下
    2023-08-08
  • vue中的cookies缓存存值方式 超简单

    vue中的cookies缓存存值方式 超简单

    这篇文章主要介绍了vue中的cookies缓存存值方式,超简单!具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • vue使用moment如何将时间戳转为标准日期时间格式

    vue使用moment如何将时间戳转为标准日期时间格式

    这篇文章主要介绍了vue使用moment如何将时间戳转为标准日期时间格式问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • vue element-ui table表格滚动加载方法

    vue element-ui table表格滚动加载方法

    下面小编就为大家分享一篇vue element-ui table表格滚动加载方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-03-03
  • Vue3中watch的使用详解

    Vue3中watch的使用详解

    这篇文章主要介绍了Vue3中watch的详解,主要包括Vue2使用watch及Vue3使用watch的方法,通过多种情况实例代码相结合给大家详细讲解,需要的朋友可以参考下
    2022-11-11

最新评论