最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue3實(shí)現(xiàn)數(shù)字滾動(dòng)特效實(shí)例詳解

 更新時(shí)間:2022年09月07日 17:12:47   作者:承吾  
這篇文章主要為大家介紹了vue3實(shí)現(xiàn)數(shù)字滾動(dòng)特效實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

vue3不支持vue-count-to插件,無(wú)法使用vue-count-to實(shí)現(xiàn)數(shù)字動(dòng)效,數(shù)字自動(dòng)分割,vue-count-to主要針對(duì)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)效。先來(lái)看效果圖:

思路

使用Vue.component定義公共組件,使用window.requestAnimationFrame(首選,次選setTimeout)來(lái)循環(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" // 過(guò)渡時(shí)間
 prefix="¥"   // 前綴符號(hào)
 suffix="rmb" // 后綴符號(hào)
 />

入口文件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;
// 通過(guò)遍歷各瀏覽器前綴,來(lái)得到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
    // 通過(guò)遍歷各瀏覽器前綴,來(lái)得到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ù),對(duì)數(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新的語(yǔ)法糖, 編寫代碼更加簡(jiǎn)潔高效
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ù)來(lái)驅(qū)動(dòng)頁(yè)面渲染,防止頁(yè)面出現(xiàn)卡頓,不要強(qiáng)行操作dom,引入的組件可以全局配置,后續(xù)組件可以服用

demo演示

后續(xù)的線上demo演示會(huì)放在 demo演示 

以上就是vue3實(shí)現(xiàn)數(shù)字滾動(dòng)特效實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于vue3數(shù)字滾動(dòng)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue渲染函數(shù)詳解

    Vue渲染函數(shù)詳解

    下面小編就為大家?guī)?lái)一篇Vue渲染函數(shù)詳解。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • vue環(huán)境如何實(shí)現(xiàn)div?focus?blur焦點(diǎn)事件

    vue環(huán)境如何實(shí)現(xiàn)div?focus?blur焦點(diǎn)事件

    這篇文章主要介紹了vue環(huán)境如何實(shí)現(xiàn)div?focus?blur焦點(diǎn)事件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • Vue駝峰與短橫線分割命名中有哪些坑

    Vue駝峰與短橫線分割命名中有哪些坑

    這篇文章主要介紹了Vue駝峰與短橫線分割命名中的注意事項(xiàng),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2023-02-02
  • vue3觸發(fā)父組件兩種寫法

    vue3觸發(fā)父組件兩種寫法

    這篇文章主要介紹了vue3觸發(fā)父組件兩種寫法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • vue 2 實(shí)現(xiàn)自定義組件一到多個(gè)v-model雙向數(shù)據(jù)綁定的方法(最新推薦)

    vue 2 實(shí)現(xiàn)自定義組件一到多個(gè)v-model雙向數(shù)據(jù)綁定的方法(最新推薦)

    有時(shí)候我們需要對(duì)一個(gè)組件綁定自定義 v-model,以更方便地實(shí)現(xiàn)雙向數(shù)據(jù),例如自定義表單輸入控件,這篇文章主要介紹了vue 2 實(shí)現(xiàn)自定義組件一到多個(gè)v-model雙向數(shù)據(jù)綁定的方法,需要的朋友可以參考下
    2024-07-07
  • Vue組件設(shè)計(jì)之多列表拖拽交換排序功能實(shí)現(xiàn)

    Vue組件設(shè)計(jì)之多列表拖拽交換排序功能實(shí)現(xiàn)

    這篇文章主要介紹了Vue組件設(shè)計(jì)之多列表拖拽交換排序,常見(jiàn)的場(chǎng)景有單列表拖拽排序,多列表拖拽交換排序,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-05-05
  • vue如何動(dòng)態(tài)給img賦值

    vue如何動(dòng)態(tài)給img賦值

    這篇文章主要介紹了vue如何動(dòng)態(tài)給img賦值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Vue項(xiàng)目報(bào)錯(cuò):Uncaught SyntaxError: Unexpected token <

    Vue項(xiàng)目報(bào)錯(cuò):Uncaught SyntaxError: Unexpected token <

    這篇文章主要介紹了Vue項(xiàng)目報(bào)錯(cuò):Uncaught SyntaxError: Unexpected token <,在引入第三方依賴的 JS 文件時(shí),遇到的一個(gè)問(wèn)題,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-11-11
  • Vue3中使用pinia的示例代碼

    Vue3中使用pinia的示例代碼

    這篇文章主要介紹了Vue3中使用pinia,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-12-12
  • Vue+Vant實(shí)現(xiàn)頂部搜索欄

    Vue+Vant實(shí)現(xiàn)頂部搜索欄

    這篇文章主要為大家詳細(xì)介紹了Vue+Vant實(shí)現(xiàn)頂部搜索欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-06-06

最新評(píng)論

罗源县| 彭水| 宣化县| 安塞县| 宿迁市| 万宁市| 鹤庆县| 藁城市| 富川| 郑州市| 平和县| 南澳县| 福建省| 城固县| 吐鲁番市| 图们市| 兴和县| 乡宁县| 比如县| 商南县| 九龙城区| 基隆市| 巴中市| 万宁市| 新安县| 临泽县| 论坛| 仪陇县| 甘泉县| 南岸区| 隆德县| 湟中县| 武邑县| 顺义区| 浏阳市| 慈利县| 长沙县| 涿鹿县| 井陉县| 皮山县| 蓬莱市|