首页IT科技vue实现滚动条(vue3 | 数据可视化实现数字滚动特效)

vue实现滚动条(vue3 | 数据可视化实现数字滚动特效)

时间2025-05-05 04:52:27分类IT科技浏览3609
导读:前言 vue3不支持vue-count-to插件,无法使用vue-count-to实现数字动效,数字自动分割,vue-count-to主要针对vue2使用,vue3按照会报错:...

前言

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     。

文件目录

使用示例

<CountTo :start="0" // 从数字多少开始 :end="endCount" // 到数字多少结束 :autoPlay="true" // 自动播放 :duration="3000" // 过渡时间 prefix="¥" // 前缀符号 suffix="rmb" // 后缀符号 />

入口文件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 < prefixes.length; i++) { if (requestAnimationFrame && cancelAnimationFrame) { break } prefix = prefixes[i] requestAnimationFrame = requestAnimationFrame || window[prefix + RequestAnimationFrame] cancelAnimationFrame = cancelAnimationFrame || window[prefix + CancelAnimationFrame] || window[prefix + CancelRequestAnimationFrame] } //不支持使用setTimeout方式替换:模拟60帧的效果 // 如果当前浏览器不支持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) } }

完整代码:

requestAnimationFrame.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 }

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.start state.startTime = null state.localDuration = props.duration state.paused = false state.rAF = requestAnimationFrame(count) }

核心函数      ,对数字进行转动

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) } } // 格式化数据               ,返回想要展示的数据格式 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 }

取消动效

// 组件销毁时取消动画 onUnmounted(() => { cancelAnimationFrame(state.rAF) })

完整代码

<template> {{ state.displayValue }} </template> <script setup> // vue3.2新的语法糖, 编写代码更加简洁高效 import { onMounted, onUnmounted, reactive } from "@vue/runtime-core"; import { watch, computed } from vue; 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 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 }) // 定义一个计算属性    ,当开始数字大于结束数字时返回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>

总结

自己封装数字动态效果需要注意各个浏览器直接的差异           ,手动pollyfill  ,暴露出去的props参数需要有默认值             ,数据的格式化可以才有正则表达式的方式              ,组件的驱动必须是数据变化,根据数据来驱动页面渲染          ,防止页面出现卡顿                ,不要强行操作dom,引入的组件可以全局配置  ,后续组件可以服用        ,码字不易                ,请各位看官大佬多多支持     ,一键三连了~❤️❤️❤️

demo演示

后续的线上demo演示会放在

demo演示

完整代码会放在

个人主页

希望对vue开发者有所帮助~

个人简介:承吾 工作年限:5年前端 地区:上海 个人宣言:立志出好文      ,传播我所会的               ,有好东西就及时与大家共享!

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
seo关键词排名优化教程(SEO关键词排名工具——提升网站排名的利器) 服务器1302(探索1136服务器主机的性能改进与优化方法)