vue3數(shù)據(jù)可視化實(shí)現(xiàn)數(shù)字滾動(dòng)特效代碼
前言
vue3不支持vue-count-to插件,無法使用vue-count-to實(shí)現(xiàn)數(shù)字動(dòng)效,數(shù)字自動(dòng)分割,vue-count-to主要針對vue2使用,vue3按照會(huì)報(bào)錯(cuò):TypeError: Cannot read properties of undefined (reading '_c')
的錯(cuò)誤信息。這個(gè)時(shí)候我們只能自己封裝一個(gè)CountTo組件實(shí)現(xiàn)數(shù)字動(dòng)效。先來看效果圖:
思路
使用Vue.component定義公共組件,使用window.requestAnimationFrame(首選,次選setTimeout)來循環(huán)數(shù)字動(dòng)畫,window.cancelAnimationFrame取消數(shù)字動(dòng)畫效果,封裝一個(gè)requestAnimationFrame.js公共文件,CountTo.vue組件,入口導(dǎo)出文件index.js。
文件目錄
使用示例
<CountTo :start="0" // 從數(shù)字多少開始 :end="endCount" // 到數(shù)字多少結(jié)束 :autoPlay="true" // 自動(dòng)播放 :duration="3000" // 過渡時(shí)間 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思路
- 先判斷是不是瀏覽器還是其他環(huán)境
- 如果是瀏覽器判斷瀏覽器內(nèi)核類型
- 如果瀏覽器不支持requestAnimationFrame,cancelAnimationFrame方法,改寫setTimeout定時(shí)器
- 導(dǎo)出兩個(gè)方法 requestAnimationFrame, cancelAnimationFrame
各個(gè)瀏覽器前綴:let prefixes = 'webkit moz ms o'; 判斷是不是瀏覽器:let isServe = typeof window == 'undefined'; 增加各個(gè)瀏覽器前綴: let prefix; let requestAnimationFrame; let cancelAnimationFrame; // 通過遍歷各瀏覽器前綴,來得到requestAnimationFrame和cancelAnimationFrame在當(dāng)前瀏覽器的實(shí)現(xiàn)形式 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幀的效果 // 如果當(dāng)前瀏覽器不支持requestAnimationFrame和cancelAnimationFrame,則會(huì)退到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 // 判斷是否是服務(wù)器環(huán)境 const isServer = typeof window === 'undefined' if (isServer) { requestAnimationFrame = function () { return } cancelAnimationFrame = function () { return } } else { requestAnimationFrame = window.requestAnimationFrame cancelAnimationFrame = window.cancelAnimationFrame let prefix // 通過遍歷各瀏覽器前綴,來得到requestAnimationFrame和cancelAnimationFrame在當(dāng)前瀏覽器的實(shí)現(xiàn)形式 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'] } // 如果當(dāng)前瀏覽器不支持requestAnimationFrame和cancelAnimationFrame,則會(huì)退到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函數(shù),還需要格式化數(shù)字,進(jìn)行正則表達(dá)式轉(zhuǎn)換,返回我們想要的數(shù)據(jù)格式。
引入 import { requestAnimationFrame, cancelAnimationFrame } from './requestAnimationFrame.js'
需要接受的參數(shù):
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; } } })
啟動(dòng)數(shù)字動(dòng)效
const startCount = () => { state.localStart = props.start state.startTime = null state.localDuration = props.duration state.paused = false state.rAF = requestAnimationFrame(count) }
核心函數(shù),對數(shù)字進(jìn)行轉(zhuǎn)動(dòng)
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') } } // 格式化數(shù)據(jù),返回想要展示的數(shù)據(jù)格式 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 }
取消動(dòng)效
// 組件銷毀時(shí)取消動(dòng)畫 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' // 定義父組件傳遞的參數(shù) 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)) } // 格式化數(shù)據(jù),返回想要展示的數(shù)據(jù)格式 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 } // 相當(dāng)于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 }) // 定義一個(gè)計(jì)算屬性,當(dāng)開始數(shù)字大于結(jié)束數(shù)字時(shí)返回true const stopCount = computed(() => { return props.start > props.end }) // 定義父組件的自定義事件,子組件以觸發(fā)父組件的自定義事件 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掛在完成后執(zhí)行一些操作 onMounted(() => { if (props.autoPlay) { startCount() } emits('onMountedcallback') }) // 暫停計(jì)數(shù) const pause = () => { cancelAnimationFrame(state.rAF) } // 恢復(fù)計(jì)數(shù) 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') } } // 組件銷毀時(shí)取消動(dòng)畫 onUnmounted(() => { cancelAnimationFrame(state.rAF) }) </script>
總結(jié)
自己封裝數(shù)字動(dòng)態(tài)效果需要注意各個(gè)瀏覽器直接的差異,手動(dòng)pollyfill,暴露出去的props參數(shù)需要有默認(rèn)值,數(shù)據(jù)的格式化可以才有正則表達(dá)式的方式,組件的驅(qū)動(dòng)必須是數(shù)據(jù)變化,根據(jù)數(shù)據(jù)來驅(qū)動(dòng)頁面渲染,防止頁面出現(xiàn)卡頓,不要強(qiáng)行操作dom,引入的組件可以全局配置,后續(xù)組件可以服用。
到此這篇關(guān)于vue3數(shù)據(jù)可視化實(shí)現(xiàn)數(shù)字滾動(dòng)特效的文章就介紹到這了,更多相關(guān)vue3數(shù)據(jù)可視化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue項(xiàng)目本地沒有問題但部署到服務(wù)器上提示錯(cuò)誤(問題解決方案)
一個(gè) VUE 的項(xiàng)目在本地部署沒有問題,但是部署到服務(wù)器上的時(shí)候提示訪問資源的錯(cuò)誤,遇到這樣的問題如何解決呢?下面小編給大家?guī)砹薞ue項(xiàng)目本地沒有問題但部署到服務(wù)器上提示錯(cuò)誤的解決方法,感興趣的朋友一起看看吧2023-05-05vue項(xiàng)目中一定會(huì)用到的性能優(yōu)化技巧
這篇文章主要為大家介紹了vue項(xiàng)目中一定會(huì)用到的性能優(yōu)化技巧實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07在vue中更換字體,本地存儲(chǔ)字體非引用在線字體庫的方法
今天小編就為大家分享一篇在vue中更換字體,本地存儲(chǔ)字體非引用在線字體庫的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09van-dialog 組件調(diào)用報(bào)錯(cuò)的解決
這篇文章主要介紹了van-dialog 組件調(diào)用報(bào)錯(cuò)的解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05Vue3響應(yīng)式高階用法之shallowReadonly()使用
在前端開發(fā)中,Vue3的shallowReadonly() API允許開發(fā)者創(chuàng)建部分只讀的狀態(tài),這對于保持頂層屬性不變而內(nèi)部屬性可變的場景非常有用,本文將詳細(xì)介紹?shallowReadonly()?的使用方法及其應(yīng)用場景,具有一定的參考價(jià)值,感興趣的可以了解一下2024-09-09vue3?element?plus?table?selection展示數(shù)據(jù),默認(rèn)選中功能方式
這篇文章主要介紹了vue3?element?plus?table?selection展示數(shù)據(jù),默認(rèn)選中功能方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07