1.新建组件vue-countTo.vue
<template>
<span>
{{ state.displayValue }}
</span>
</template>
<script setup>
import { onMounted, onUnmounted, reactive, watch, computed } from "vue";
import {
requestAnimationFrame,
cancelAnimationFrame,
} from "./animationFrame.js";
const props = defineProps({
start: {
type: Number,
required: false,
default: 0,
},
end: {
type: Number,
required: false,
default: 2021,
},
duration: {
type: Number,
required: false,
default: 5000,
},
autoPlay: {
type: Boolean,
required: false,
default: true,
},
decimals: {
type: Number,
required: false,
default: 0,
validator(value) {
return value >= 0;
},
},
decimal: {
type: String,
required: false,
default: ".",
},
separator: {
type: String,
required: false,
default: ",",
},
prefix: {
type: String,
required: false,
default: "",
},
suffix: {
type: String,
required: false,
default: "",
},
useEasing: {
type: Boolean,
required: false,
default: true,
},
easingFn: {
type: Function,
default(t, b, c, d) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
},
},
});
const isNumber = (val) => {
return !isNaN(parseFloat(val));
};
const formatNumber = (val) => {
if (props.default) {
val = val.toFixed(props.default);
val += '';
const x = val.split('.');
let x1 = x[0];
const x2 = x.length > 1 ? props.decimal + x[1] : '';
const rgx = /(\d+)(\d{3})/;
if (props.separator && !isNumber(props.separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + props.separator + '$2');
}
}
return props.prefix + x1 + x2 + props.suffix;
} else {
return Math.ceil(val);
}
};
const state = reactive({
localStart: props.start,
displayValue: formatNumber(props.start),
printVal: null,
paused: false,
localDuration: props.duration,
startTime: null,
timestamp: null,
remaining: null,
rAF: null,
});
const stopCount = computed(() => {
return props.start > props.end;
});
const emits = defineEmits(["onMountedcallback", "callback"]);
const startCount = () => {
state.localStart = props.start;
state.startTime = null;
state.localDuration = props.duration;
state.paused = false;
state.rAF = requestAnimationFrame(count);
};
watch(
() => props.start,
() => {
if (props.autoPlay) {
startCount();
}
}
);
watch(
() => props.end,
() => {
console.log(111);
if (props.autoPlay) {
startCount();
}
}
);
onMounted(() => {
if (props.autoPlay) {
startCount();
}
emits("onMountedcallback");
});
const pause = () => {
cancelAnimationFrame(state.rAF);
};
const resume = () => {
state.startTime = null;
state.localDuration = +state.remaining;
state.localStart = +state.printVal;
requestAnimationFrame(count);
};
const pauseResume = () => {
if (state.paused) {
resume();
state.paused = false;
} else {
pause();
state.paused = true;
}
};
const reset = () => {
state.startTime = null;
cancelAnimationFrame(state.rAF);
state.displayValue = formatNumber(props.start);
};
const count = (timestamp) => {
if (!state.startTime) state.startTime = timestamp;
state.timestamp = timestamp;
const progress = timestamp - state.startTime;
state.remaining = state.localDuration - progress;
if (props.useEasing) {
if (stopCount.value) {
state.printVal =
state.localStart -
props.easingFn(
progress,
0,
state.localStart - props.end,
state.localDuration
);
} else {
state.printVal = props.easingFn(
progress,
state.localStart,
props.end - state.localStart,
state.localDuration
);
}
} else {
if (stopCount.value) {
state.printVal =
state.localStart -
(state.localStart - props.end) * (progress / state.localDuration);
} else {
state.printVal =
state.localStart +
(props.end - state.localStart) * (progress / state.localDuration);
}
}
if (stopCount.value) {
state.printVal = state.printVal < props.end ? props.end : state.printVal;
} else {
state.printVal = state.printVal > props.end ? props.end : state.printVal;
}
state.displayValue = formatNumber(state.printVal);
if (progress < state.localDuration) {
state.rAF = requestAnimationFrame(count);
} else {
emits("callback");
}
};
onUnmounted(() => {
cancelAnimationFrame(state.rAF);
});
</script>
2.引入animationFrame.js
let lastTime = 0
const prefixes = 'webkit moz ms o'.split(' ')
let requestAnimationFrame
let cancelAnimationFrame
const isServer = typeof window === 'undefined'
if (isServer) {
requestAnimationFrame = function() {
return
}
cancelAnimationFrame = function() {
return
}
} else {
requestAnimationFrame = window.requestAnimationFrame
cancelAnimationFrame = window.cancelAnimationFrame
let prefix
for (let i = 0; i < prefixes.length; i++) {
if (requestAnimationFrame && cancelAnimationFrame) { break }
prefix = prefixes[i]
requestAnimationFrame = requestAnimationFrame || window[prefix + 'RequestAnimationFrame']
cancelAnimationFrame = cancelAnimationFrame || window[prefix + 'CancelAnimationFrame'] || window[prefix + 'CancelRequestAnimationFrame']
}
if (!requestAnimationFrame || !cancelAnimationFrame) {
requestAnimationFrame = function(callback) {
const currTime = new Date().getTime()
const timeToCall = Math.max(0, 16 - (currTime - lastTime))
const id = window.setTimeout(() => {
callback(currTime + timeToCall)
}, timeToCall)
lastTime = currTime + timeToCall
return id
}
cancelAnimationFrame = function(id) {
window.clearTimeout(id)
}
}
}
export { requestAnimationFrame, cancelAnimationFrame }
3.页面上使用
<countTo :end='end' :autoPlay="true" :duration='3000' />