UNPKG

vue3-countdown

Version:

A simple countdown component for Vue3.x.

170 lines (165 loc) 5.93 kB
/** * vue3-countdown v1.0.5 * (c) 2021 wuanrin * Released under the MIT license. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vue')) : typeof define === 'function' && define.amd ? define(['vue'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Vue3Countdown = factory(global.Vue)); }(this, (function (vue) { 'use strict'; /** * Utils */ const units = [ { symbol: 'D', value: 24 * 60 * 60 * 1000 }, { symbol: 'H', value: 60 * 60 * 1000 }, { symbol: 'm', value: 60 * 1000 }, { symbol: 's', value: 1000 }, { symbol: 'S', value: 100 } ]; function replaceAll(source, search, replace) { const escapeStr = '~' + search; if (source.indexOf(escapeStr) > -1) { return source .split(escapeStr) .map(part => replaceAll(part, search, replace)) .join(escapeStr); } return source.replace(new RegExp(search, 'g'), String(replace)); } // Decompose time into time units function resolveCountdown(countdown, format = 'HH:mm:ss') { const res = {}; if (format.indexOf('~') > -1) { format = format.replace(/~[DHmsS]/g, ''); } const thisUnits = units.filter(unit => format.indexOf(unit.symbol) > -1); for (let i = 0, l = thisUnits.length; i < l; i++) { const { symbol, value } = thisUnits[i]; // Use ceil method when only unit const unitValue = (l === 1 ? Math.ceil(countdown / value) : Math.floor(countdown / value)); res[symbol] = unitValue; res[symbol + symbol] = unitValue < 10 ? `0${unitValue}` : unitValue; countdown %= value; } return res; } // Format countdown function formatCountdown(countdown, format = 'HH:mm:ss') { // Check whether resolved const time = (typeof countdown === 'number' ? resolveCountdown(countdown, format) : countdown); // Sort the keys from long to short to replace long placeholders first // to prevent long placeholders from being replaced by short ones const keys = Object.keys(time).sort((a, b) => b.length - a.length); let rs = format; keys.forEach(key => { rs = replaceAll(rs, key, time[key]); }); // Remove escape char if (rs.indexOf('~') > -1) { rs = rs.replace(/~([DHmsS])/g, '$1'); } return rs; } var Countdown = vue.defineComponent({ name: 'Countdown', emits: ['change', 'finish'], props: { // Countdown time. ms time: { type: Number, default: 0 }, format: { type: String, default: 'HH:mm:ss' }, autoStart: { type: Boolean, default: true } }, setup(props, { emit }) { const currentTime = vue.ref(props.time); const resolved = vue.ref(resolveCountdown(currentTime.value, props.format)); const inCountdown = vue.ref(false); const config = { restTime: props.time, now: 0, timer: 0 }; // computed const stepInterval = vue.computed(() => /s/i.test(props.format) ? 30 : 1000); const formatted = vue.computed(() => formatCountdown(resolved.value, props.format)); // methods const start = () => { if (!inCountdown.value) { config.now = Date.now(); config.restTime = currentTime.value; window.clearInterval(config.timer); config.timer = window.setInterval(step, stepInterval.value); inCountdown.value = true; } }; const step = () => { const { restTime, now } = config; const interval = Date.now() - now; const target = restTime > interval ? restTime - interval : 0; const thisResolved = resolveCountdown(target, props.format); const thisFormatted = formatCountdown(thisResolved, props.format); if (thisFormatted !== formatted.value) { emit('change', { currentTime: target, resolved: thisResolved, formatted: thisFormatted }); resolved.value = thisResolved; } currentTime.value = target; if (restTime <= interval) { stop(); } }; const stop = () => { doStop(); emit('finish'); }; const doStop = () => { inCountdown.value = false; window.clearInterval(config.timer); }; const reset = () => { currentTime.value = config.restTime = props.time; resolved.value = resolveCountdown(props.time, props.format); doStop(); }; if (props.autoStart) { start(); } return { currentTime, resolved, formatted, inCountdown, start, stop, reset }; }, render() { const { currentTime, resolved, formatted } = this; return vue.h('div', null, (this.$slots.default && this.$slots.default({ currentTime, resolved, formatted })) || this.formatted); } }); return Countdown; })));