import { defineComponent, reactive, computed, watch, onMounted, unref } from "vue"; import { countToProps } from "./props"; import { isNumber } from "/@/utils/is"; export default defineComponent({ name: "CountTo", props: countToProps, emits: ["mounted", "callback"], setup(props, { emit }) { const state = reactive<{ localStartVal: number; printVal: number | null; displayValue: string; paused: boolean; localDuration: number | null; startTime: number | null; timestamp: number | null; rAF: any; remaining: number | null; color: string; fontSize: string; }>({ localStartVal: props.startVal, displayValue: formatNumber(props.startVal), printVal: null, paused: false, localDuration: props.duration, startTime: null, timestamp: null, remaining: null, rAF: null, color: null, fontSize: "16px" }); const getCountDown = computed(() => { return props.startVal > props.endVal; }); watch([() => props.startVal, () => props.endVal], () => { if (props.autoplay) { start(); } }); function start() { const { startVal, duration, color, fontSize } = props; state.localStartVal = startVal; state.startTime = null; state.localDuration = duration; state.paused = false; state.color = color; state.fontSize = fontSize; state.rAF = requestAnimationFrame(count); } // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars function pauseResume() { if (state.paused) { resume(); state.paused = false; } else { pause(); state.paused = true; } } function pause() { cancelAnimationFrame(state.rAF); } function resume() { state.startTime = null; state.localDuration = +(state.remaining as number); state.localStartVal = +(state.printVal as number); requestAnimationFrame(count); } // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars function reset() { state.startTime = null; cancelAnimationFrame(state.rAF); state.displayValue = formatNumber(props.startVal); } function count(timestamp: number) { const { useEasing, easingFn, endVal } = props; if (!state.startTime) state.startTime = timestamp; state.timestamp = timestamp; const progress = timestamp - state.startTime; state.remaining = (state.localDuration as number) - progress; if (useEasing) { if (unref(getCountDown)) { state.printVal = state.localStartVal - easingFn( progress, 0, state.localStartVal - endVal, state.localDuration as number ); } else { state.printVal = easingFn( progress, state.localStartVal, endVal - state.localStartVal, state.localDuration as number ); } } else { if (unref(getCountDown)) { state.printVal = state.localStartVal - (state.localStartVal - endVal) * (progress / (state.localDuration as number)); } else { state.printVal = state.localStartVal + (endVal - state.localStartVal) * (progress / (state.localDuration as number)); } } if (unref(getCountDown)) { state.printVal = state.printVal < endVal ? endVal : state.printVal; } else { state.printVal = state.printVal > endVal ? endVal : state.printVal; } state.displayValue = formatNumber(state.printVal); if (progress < (state.localDuration as number)) { state.rAF = requestAnimationFrame(count); } else { emit("callback"); } } function formatNumber(num: number | string) { const { decimals, decimal, separator, suffix, prefix } = props; num = Number(num).toFixed(decimals); num += ""; const x = num.split("."); let x1 = x[0]; const x2 = x.length > 1 ? decimal + x[1] : ""; const rgx = /(\d+)(\d{3})/; if (separator && !isNumber(separator)) { while (rgx.test(x1)) { x1 = x1.replace(rgx, "$1" + separator + "$2"); } } return prefix + x1 + x2 + suffix; } onMounted(() => { if (props.autoplay) { start(); } emit("mounted"); }); return () => ( <> {state.displayValue} ); } });