vue3-countdown
Version:
A simple countdown component for Vue3.x.
170 lines (165 loc) • 5.93 kB
JavaScript
/**
* 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;
})));