当前位置 : 主页 > 网络编程 > JavaScript >

vue3数据可视化实现数字滚动特效代码

来源:互联网 收集:自由互联 发布时间:2023-02-08
目录 前言 思路 文件目录 使用示例 入口文件index.js main.js使用 requestAnimationFrame.js思路 完整代码: CountTo.vue组件思路 总结 前言 vue3不支持vue-count-to插件,无法使用vue-count-to实现数字动效
目录
  • 前言
  • 思路
    • 文件目录
  • 使用示例
    • 入口文件index.js
      • main.js使用
        • requestAnimationFrame.js思路
          • 完整代码:
        • CountTo.vue组件思路
          • 总结

            前言

            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演示

            到此这篇关于vue3数据可视化实现数字滚动特效的文章就介绍到这了,更多相关vue3数据可视化内容请搜索易盾网络以前的文章或继续浏览下面的相关文章希望大家以后多多支持易盾网络!

            上一篇:React中编写CSS实例详解
            下一篇:没有了
            网友评论