前言
vue3不支持vue-count-to插件,无法使用vue-count-to实现数字动效,数字自动分割,vue-count-to主要针对vue2使用,vue3按照会报错:
TypeError: Cannot read properties of undefined (reading '_c')
的错误信息。这个时候我们只能自己封装一个CountTo组件实现数字动效。先来看效果图:
思路
使用Vue.component定义公共组件,使用window.requestAnimationFrame(首选,次选setTimeout)来循环数字动画,window.cancelAnimationFrame取消数字动画效果,封装一个requestAnimationFrame.js公共文件,CountTo.vue组件,入口导出文件index.js。
文件目录
使用示例
入口文件index.js
const UILib = {install(Vue) {Vue.component('CountTo', CountTo)}}export default UILib
main.js使用
import CountTo from './components/count-to/index';app.use(CountTo)
requestAnimationFrame.js思路
- 先判断是不是浏览器还是其他环境
- 如果是浏览器判断浏览器内核类型
- 如果浏览器不支持requestAnimationFrame,cancelAnimationFrame方法,改写setTimeout定时器
- 导出两个方法 requestAnimationFrame, cancelAnimationFrame
各个浏览器前缀:let prefixes ='webkit moz ms o';判断是不是浏览器:let isServe = typeof window == 'undefined';增加各个浏览器前缀:let prefix;let requestAnimationFrame;let cancelAnimationFrame;// 通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式for (let i = 0; i {callback(currTime + timeToCall)}, timeToCall)lastTime = currTime + timeToCallreturn id}cancelAnimationFrame = function (id) {window.clearTimeout(id)}}
完整代码:
requestAnimationFrame.js
let lastTime = 0const prefixes = 'webkit moz ms o'.split(' ') // 各浏览器前缀let requestAnimationFramelet cancelAnimationFrame// 判断是否是服务器环境const isServer = typeof window === 'undefined'if (isServer) {requestAnimationFrame = function () {return}cancelAnimationFrame = function () {return}} else {requestAnimationFrame = window.requestAnimationFramecancelAnimationFrame = window.cancelAnimationFramelet prefix// 通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式for (let i = 0; i {callback(currTime + timeToCall)}, timeToCall)lastTime = currTime + timeToCallreturn id}cancelAnimationFrame = function (id) {window.clearTimeout(id)}}}export { requestAnimationFrame, cancelAnimationFrame }
CountTo.vue组件思路
首先引入requestAnimationFrame.js,使用requestAnimationFrame方法接受count函数,还需要格式化数字,进行正则表达式转换,返回我们想要的数据格式。
引入 import { requestAnimationFrame, cancelAnimationFrame } from './requestAnimationFrame.js'
需要接受的参数:
const props = defineProps({start: {type: Number,required: false,default: 0},end: {type: Number,required: false,default: 0},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 startCount = () => {state.localStart = props.startstate.startTime = nullstate.localDuration = props.durationstate.paused = falsestate.rAF = requestAnimationFrame(count)}
核心函数,对数字进行转动
if (!state.startTime) state.startTime = timestampstate.timestamp = timestampconst progress = timestamp - state.startTimestate.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 = 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) => {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}// 相当于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})// 定义一个计算属性,当开始数字大于结束数字时返回trueconst stopCount = computed(() => {return props.start > props.end})// 定义父组件的自定义事件,子组件以触发父组件的自定义事件const emits = defineEmits(['onMountedcallback', 'callback'])const startCount = () => {state.localStart = props.startstate.startTime = nullstate.localDuration = props.durationstate.paused = falsestate.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 = nullstate.localDuration = +state.remainingstate.localStart = +state.printValrequestAnimationFrame(count)}const pauseResume = () => {if (state.paused) {resume()state.paused = false} else {pause()state.paused = true}}const reset = () => {state.startTime = nullcancelAnimationFrame(state.rAF)state.displayValue = formatNumber(props.start)}const count = (timestamp) => {if (!state.startTime) state.startTime = timestampstate.timestamp = timestampconst progress = timestamp - state.startTimestate.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}state.displayValue = formatNumber(state.printVal)if (progress {cancelAnimationFrame(state.rAF)})
总结
自己封装数字动态效果需要注意各个浏览器直接的差异,手动pollyfill,暴露出去的props参数需要有默认值,数据的格式化可以才有正则表达式的方式,组件的驱动必须是数据变化,根据数据来驱动页面渲染,防止页面出现卡顿,不要强行操作dom,引入的组件可以全局配置,后续组件可以服用,码字不易,请各位看官大佬多多支持,一键三连了~❤️❤️❤️
demo演示
后续的线上demo演示会放在
demo演示
完整代码会放在
个人主页
希望对vue开发者有所帮助~
- 个人简介:承吾
- 工作年限:5年前端
- 地区:上海
- 个人宣言:立志出好文,传播我所会的,有好东西就及时与大家共享!