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

目录
  • 前言
  • 思路
    • 文件目录
  • 使用示例
  • 入口文件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数据可视化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue.js实现大屏数字滚动翻转效果

    大屏数字滚动翻转效果来源于最近工作中element后台管理页面一张大屏的UI图,该UI图上有一个模块需要有数字往上翻动的效果,以下是最终实现的效果: 整体思路: 在实现此效果之前,我们先来捋一下思路,用思维导图来设计一下我们的实现步骤,如下: 你可以审查元素,下载数字背景图片,复制图片地址,或者使用其他背景图片.背景颜色 有了以上的设计流程,我们先来简单实现一下: // CSS代码 <style> .box-item { position: relative; display: inline-

  • Vue使用Echarts实现数据可视化的方法详解

    目录 一,Echarts 1.1 获取ECharts 1.2 引入 ECharts 二,Vue使用Echarts 2.1 Vue环境 2.2 main.js引入Echarts 2.3 使用模板 2.4实例 2.4.1柱状图(折线图变换) 2.4.2极坐标柱状图标签 总结 一,Echarts 一个基于 JavaScript 的开源可视化图表库 Echarts官网 https://echarts.apache.org/zh/index.html 1.1 获取ECharts (1)从 npm 获取(项

  • vue基于Echarts的拖拽数据可视化功能实现

    背景 我司产品提出了一个需求,做一个数据基于Echars的可拖拽缩放的数据可视化,上网百度了一番,结果出现了两种结局,一种花钱买成熟产品(公司不出钱),一种没有成熟代码,只能自己写了,故事即将开始,敬请期待.......  不,还是先上一张效果图吧,请看...... 前期知识点 1. offset(偏移量) 定义:当前元素在屏幕上占用的空间,如下图: 其中: offsetHeight: 该元素在垂直方向上的占用的空间,单位为px,不包括margin. offsetWidth:该元素在水平方向上的

  • vue实现数字滚动效果

    本文实例为大家分享了vue实现数字滚动的具体代码,供大家参考,具体内容如下 <template> <div class="num-block"> <div class="num-block_show"> <div class="num-block_numbers" :class="{'ellipsis': !isNum(item)}" v-for="(item, key) i

  • 基于vue+echarts 数据可视化大屏展示的方法示例

    获取 ECharts 的路径有以下几种,请根据您的情况进行选择: 1) 最直接的方法是在 ECharts 的官方网站中挑选适合您的版本进行下载,不同的打包下载应用于不同的开发者功能与体积的需求,或者您也可以直接下载完整版本:开发环境建议下载源代码版本,包含了常见的错误提示和警告. 2) 也可以在 ECharts 的 GitHub 上下载最新的 release 版本,解压出来的文件夹里的 dist 目录里可以找到最新版本的 echarts 库. 3) 或者通过 npm 获取 echarts,npm

  • 基于vue+echarts数据可视化大屏展示的实现

    获取 ECharts 的路径有以下几种,请根据您的情况进行选择: 1) 最直接的方法是在 ECharts 的官方网站中挑选适合您的版本进行下载,不同的打包下载应用于不同的开发者功能与体积的需求,或者您也可以直接下载完整版本:开发环境建议下载源代码版本,包含了常见的错误提示和警告. 2) 也可以在 ECharts 的 GitHub 上下载最新的 release 版本,解压出来的文件夹里的 dist 目录里可以找到最新版本的 echarts 库. 3) 或者通过 npm 获取 echarts,npm

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

    目录 前言 思路 文件目录 使用示例 入口文件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')的错误信息.这

  • vue3实现数字滚动特效实例详解

    目录 前言 思路 文件目录 使用示例 入口文件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') 的错误信息

  • angularjs实现文字上下无缝滚动特效代码

    最近没有项目做,于是闲暇之余学习了下angularjs知识,然后写了一个文字上下无缝滚动的例子,主要写的是一个小小的指令. css代码: 主要控制样式 <style type="text/css"> *{margin: 0px;padding: 0px;} .slide {width: 200px;height:200px;border:1px solid #dcdcdc;margin: 0 auto;margin-top: 50px;overflow: hidden;}

  • 高效的jquery数字滚动特效

    本文实例讲述了基于jquery数字滚动特效的代码,分为四种情况分享给大家供大家参考,具体如下: 有分隔符,有小数点:<div class="numberRun"></div> <br><br> 只有分隔符:<div class="numberRun2"></div> <br><br> 只有小数点:<div class="numberRun3"&g

  • jQuery满屏焦点图左右滚动特效代码分享

    本文实例讲述了jQuery横向擦除焦点图特效.分享给大家供大家参考.具体如下: jQuery焦点图是一款满屏左右滚动,带左右按钮,带缩略图,支持自动轮播的焦点图代码. 运行效果图:        -------------------查看效果 下载源码------------------- 小提示:浏览器中如果不能正常运行,可以尝试切换浏览模式. 为大家分享的jQuery满屏焦点图左右滚动特效代码如下 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0

  • JavaScript实现垂直向上无缝滚动特效代码

    一.循环向上滚动的文字,如上面的滚动效果 二.实现的思路 1.建立三个层dome.dome1.dome2 2.垂直滚动的文字在dome1上 3.通过层的滚动来实现文字滚动 三.源代码 <html> <head> <title>循环向上滚动的文字</title> <link href="css/scrollTop.css" rel="stylesheet" type="text/css" /&g

  • pyecharts绘制各种数据可视化图表案例附效果+代码

    目录 1.pyecharts绘制饼图(显示百分比) 2.pyecharts绘制柱状图 3.pyecharts绘制折线图 4.pyecharts绘制柱形折线组合图 5.pyecharts绘制散点图 6.pyecharts绘制玫瑰图 7.pyecharts绘制词云图 8.pyecharts绘制雷达图 9.pyecharts绘制散点图 10.pyecharts绘制嵌套饼图 11.pyecharts绘制中国地图 12.pyecharts绘制世界地图 1.pyecharts绘制饼图(显示百分比) # 导入

  • js实现数字滚动特效

    本文实例为大家分享了js实现数字滚动展示的具体代码,供大家参考,具体内容如下 效果图 html代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> #t,#tt{ border: #ccc thin solid; width: 250px; heig

  • js文字横向滚动特效

    本文为大家分享了js文字横向滚动特效代码,具体实现内容如下: 页面布局 <div id="scroll_div" class="fl"> <div id="scroll_begin"> 恭喜793765***获得 <span class="pad_right">50元巨人点卡奖励</span> 恭喜793765***获得 <span class="pad_righ

  • jQuery合作伙伴左右滚动特效

    本文实例讲述了jQuery合作伙伴左右滚动特效,分享给大家供大家参考.具体如下: 一款基于jQuery合作伙伴左右滚动特效代码,是一款带左右箭头及自动滚动友情链接合作伙伴左右滚动网页特效,鼠标滑过停止自动滚动,鼠标离开自动滚动,点击左右按钮左右滚动Logo切换网页特效.可用于网站底部作为合作伙伴展示或友链展示的特效,是一款非常优秀的特效源码. 运行效果图:----------------------查看效果 下载源码----------------------- 小提示:浏览器中如果不能正常运行

随机推荐