效果
1.新建组件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: 888,
},
//持续时间
duration: {
type: Number,
required: false,
default: 5000,
},
//自动开始
autoPlay: {
type: Boolean,
required: false,
default: true,
},
//小数点
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;
},
},
//保留几位小数
default: {
type: Number,
required: false,
default: 0,
},
});
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);
}
};
// 相当于vue2中的data中所定义的变量部分
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,
});
// 定义一个计算属性,当开始数字大于结束数字时返回true
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,
() => {
if (props.autoPlay) {
startCount();
}
}
);
// dom挂在完成后执行一些操作
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
// 通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式
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']
}
// 如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame,则会退到setTimeout
if (!requestAnimationFrame || !cancelAnimationFrame) {
requestAnimationFrame = function(callback) {
const currTime = new Date().getTime()
// 为了使setTimteout的尽可能的接近每秒60帧的效果
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' />
4.相关api
start-amount:起始值
end-amount:结束值
duration:持续时长
prefix:前缀
suffix:尾缀
separator:分隔符(千位分隔符)
decimal-separator:小数分隔符
decimals:小数位
autoinit:是否自动执行动画
start():开始
pause():暂停
resume():恢复
reset():重置
5. 插件Vue3 AutoCounter
原网址: 访问
创建于: 2023-07-28 17:50:33
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论