SoFunction
Updated on 2025-04-05

Create a simple countdown component based on Vue3

Data that needs to be retrieved from the parent

  • time: The remaining time of the current countdown, passing seconds or milliseconds
  • isMilliSecond: Used to determine whether the current incoming value is a second or millisecond value
  • end: Used to pass in a specific end point time, second-level timestamp or millisecond-level timestamp
  • format: Used to control the final display format, default format'D day HH time MM minute SS second'
  • flag: Used to determine whether the highest value is not displayed when the highest value is 0
// 
<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 day HH time MM minute SS second',
    },
    flag: {
        type: Boolean,
        default: false,
    }
})

</script>

<template>
    <div class="count_down">
        {{ timeStr }}
    </div>
</template>

Basic variables

  • curTime: Stores the current time, because when the browser retreats to the background, it willsetTimeoutWait for the scheduled task to be paused, passcurTimeUsed to update the countdown
  • days, hours, mins, seconds: All parts of the countdown <Thinking that it cannot exceed one year's countdown>
  • timer: Storage timer
  • remainingTime: Calculate the countdown seconds
  • timeStr: Format the time string
// 
&lt;script setup lang="ts"&gt;
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: () =&gt; 'D day HH time MM minute SS second',
    },
    flag: {
        type: Boolean,
        default: false,
    }
})

let curTime = 0

const days: Ref&lt;string | number&gt; = ref('0')

const hours: Ref&lt;string | number&gt; = ref('00')

const mins: Ref&lt;string | number&gt; = ref('00')

const seconds: Ref&lt;string | number&gt; = ref('00')

let timer: any = null;

const remainingTime = computed(() =&gt; {
    if() {
        let end =  ? + : + * 1000;

        end -= ();
        return (end / 1000);
    }
    const time =  ? (+ / 1000) : (+)

    return time
})

const timeStr = computed(() =&gt; {
    const o: {
        [key: string]: any
    } = {
        'D+': ,
        'H+': ,
        'M+': ,
        'S+': ,
    }

    let str = ;

    // When the highest value is 0, the value and its units are removed. If there is a defect, it can only remove all fields before the corresponding target.    if( == 0 &amp;&amp; ) {
        let regexPattern = /.*(?=H)/;

        if( == 0) {
            regexPattern = /.*(?=M)/;

            if( == 0) {
                regexPattern = /.*(?=S)/;
            }
        }

        str = (regexPattern, '');
    }

    for (var k in o) {
        // The purpose of brackets is to capture the pattern k of the placeholder into a group so that it can be referenced when replacing the placeholder in the string.		str = (new RegExp(`(${k})`, 'g'), function(match, group) {
            let time =  === 1 ? o[k] : `00${o[k]}`.slice(-);

            // If it is a number of days, no matter what format it is, the number of days will be displayed in full, but if there are multiple Ds, 0 will be added before it is less than 10            if(k == 'D+' &amp;&amp;  &gt; 1)  {
                time = o[k];
                if(time &lt; 10) {
                    time = `0${time}`
                }
            }

            return time
        });

	}

    return str;
})
&lt;/script&gt;

&lt;template&gt;
    &lt;div class="count_down"&gt;
        {{ timeStr }}
    &lt;/div&gt;
&lt;/template&gt;

Basic Methods

  • countDown: Execute immediately after entering the pagecountDown, and executecountdown, thus starting the countdown
  • formatTime: WillremainingTimeHow to convert to days, hours, minutes, seconds
  • countdown: After obtaining the time, start countdown execution.
// 
&lt;script setup lang="ts"&gt;
const countDown = () =&gt; {
    curTime = ()
    countdown()
}

const formatTime = (time: number) =&gt; {
    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) =&gt; {
    timer &amp;&amp; clearTimeout(timer)

    if(time &lt; 0) {
        return;
    }

    const { dd, hh, mm, ss } = formatTime(time);

     = dd || 0;
     = hh || 0;
     = mm || 0;
     = ss || 0;

    timer = setTimeout(() =&gt; {
        const now = ();
        const diffTime = ((now - curTime) / 1000)

        const step = diffTime &gt; 1 ? diffTime : 1; // The page will not count down when it returns to the background. Compared with the time difference, the reset countdown for greater than 1s
        curTime = now;

        countdown(time - step);
    }, 1000);
}

onMounted(() =&gt; {
    countDown();
})
&lt;/script&gt;

Why not use setInterval to implement it

  • Inaccurate intervals:setIntervalThe interval is not guaranteed to be accurate, as it simply adds the callback function to the message queue, and the actual execution time depends on the load and event loop of the main thread and may be skipped or accumulated multiple executions.
  • Stacking Problems: If onesetIntervalThe callback executes a shorter time than its interval, then it will overlay execution. This can lead to unnecessary resource consumption and behavior that does not meet the design expectations.

These problems are usually due toJavaScriptsingle thread execution and event loop mechanisms are caused. In actual development, in order to handle timing tasks more accurately, it is usually usedsetTimeoutand recursive or computed properties to handle timing tasks. AlthoughsetIntervalThere are some limitations, but it can still come in handy in some cases, especially for some simple timing operations. However, in scenarios where more precise timing and depend on the front and back states, it is usually chosen to usesetTimeoutOr other more advanced timing management methods.

Complete code

// 
&lt;script setup lang="ts"&gt;
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: () =&gt; 'D day HH time MM minute SS second',
    },
    flag: {
        type: Boolean,
        default: false,
    }
})

let curTime = 0

const days: Ref&lt;string | number&gt; = ref('0')

const hours: Ref&lt;string | number&gt; = ref('00')

const mins: Ref&lt;string | number&gt; = ref('00')

const seconds: Ref&lt;string | number&gt; = ref('00')

let timer: any = null;

const remainingTime = computed(() =&gt; {
    if() {
        let end =  ? + : + * 1000;

        end -= ();
        return (end / 1000);
    }
    const time =  ? (+ / 1000) : (+)

    return time
})

const timeStr = computed(() =&gt; {
    const o: {
        [key: string]: any
    } = {
        'D+': ,
        'H+': ,
        'M+': ,
        'S+': ,
    }

    let str = ;

    // If the number of days is 0, I hope to remove the part before H    if( == 0 &amp;&amp; ) {
        let regexPattern = /.*(?=H)/;

        if( == 0) {
            regexPattern = /.*(?=M)/;

            if( == 0) {
                regexPattern = /.*(?=S)/;
            }
        }

        str = (regexPattern, '');
    }

    for (var k in o) {
        // The purpose of brackets is to capture the pattern k of the placeholder into a group so that it can be referenced when replacing the placeholder in the string.		str = (new RegExp(`(${k})`, 'g'), function(match, group) {
            let time =  === 1 ? o[k] : `00${o[k]}`.slice(-);

            if(k == 'D+' &amp;&amp;  &gt; 1)  {
                time = o[k];
                if(time &lt; 10) {
                    time = `0${time}`
                }
            }

            return time
        });

	}

    return str;
})

const countDown = () =&gt; {
    curTime = ()
    countdown()
}

const formatTime = (time: number) =&gt; {
    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) =&gt; {
    timer &amp;&amp; clearTimeout(timer)

    if(time &lt; 0) {
        return;
    }

    const { dd, hh, mm, ss } = formatTime(time);

     = dd || 0;
     = hh || 0;
     = mm || 0;
     = ss || 0;

    timer = setTimeout(() =&gt; {
        const now = ();
        const diffTime = ((now - curTime) / 1000)

        const step = diffTime &gt; 1 ? diffTime : 1; // The page will not count down when it returns to the background. Compared with the time difference, the reset countdown for greater than 1s
        curTime = now;

        countdown(time - step);
    }, 1000);
}

watch(remainingTime, () =&gt; {
    countDown()
}, { immediate: true })

onMounted(() =&gt; {
    countDown();
})
&lt;/script&gt;

&lt;template&gt;
    &lt;div class="count_down"&gt;
        {{ timeStr }}
    &lt;/div&gt;
&lt;/template&gt;
// Parent call&lt;script setup lang="ts"&gt;
import countDown from './components/';

&lt;/script&gt;

&lt;template&gt;
	&lt;div &gt;
		&lt;count-down 
            :end="1698980400000"
            :is-milli-second="true"
            :flag="true"
        /&gt;
	&lt;/div&gt;
&lt;/template&gt;

Disadvantages

Although this can display the corresponding format passed in by the parent, it is impossible to adjust the content or style of each unit, and it is also impossible to dynamically display different styles according to the parent. Idea: The value can be passed to the parent through slots, and the displayed content can be controlled through the parent.

After adjustment code: the basic code has no adjustment, the value will be passed to the parent through the slot.

// 
&lt;script setup lang="ts"&gt;
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&lt;string | number&gt; = ref('0')

const hours: Ref&lt;string | number&gt; = ref('00')

const mins: Ref&lt;string | number&gt; = ref('00')

const seconds: Ref&lt;string | number&gt; = ref('00')

let timer: any = null;

const remainingTime = computed(() =&gt; {
    if() {
        let end =  ? + : + * 1000;

        end -= ();
        return (end / 1000);
    }
    const time =  ? (+ / 1000) : (+)

    return time
})

const countDown = () =&gt; {
    curTime = ()
    countdown()
}

const formatTime = (time: number) =&gt; {
    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) =&gt; {
    timer &amp;&amp; clearTimeout(timer)

    if(time &lt; 0) {
        return;
    }

    const { dd, hh, mm, ss } = formatTime(time);

     = dd || 0;
     = hh || 0;
     = mm || 0;
     = ss || 0;

    timer = setTimeout(() =&gt; {
        const now = ();
        const diffTime = ((now - curTime) / 1000)

        const step = diffTime &gt; 1 ? diffTime : 1; // The page will not count down when it returns to the background. Compared with the time difference, the reset countdown for greater than 1s
        curTime = now;

        countdown(time - step);
    }, 1000);
}

watch(remainingTime, () =&gt; {
    countDown()
}, { immediate: true })

onMounted(() =&gt; {
    countDown();
})
&lt;/script&gt;

&lt;template&gt;
    &lt;div class="count_down"&gt;
        &lt;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),
        }"&gt;&lt;/slot&gt;
    &lt;/div&gt;
&lt;/template&gt;
// Parent call&lt;script setup lang="ts"&gt;
import countDown from './components/';

&lt;/script&gt;

&lt;template&gt;
	&lt;div &gt;
		&lt;count-down v-slot="timeObj" :end="1698980400000" :is-milli-second="true"&gt;
			{{}}sky{{}}Hour{{}}minute{{}}Second
		&lt;/count-down&gt;
	&lt;/div&gt;
&lt;/template&gt;

The above is the detailed content of creating a simple countdown component based on Vue3. For more information about Vue3 countdown component, please follow my other related articles!