vue3数字滚动动画_满载星海的博客-CSDN博客

效果
在这里插入图片描述

1.新建组件countTo.vue

<template>
    <span>
        {{ state.displayValue }}
    </span>
</template>

<script setup>
import { onMounted, onUnmounted, reactive, watch, computed } from "vue";
import {
    requestAnimationFrame,
    cancelAnimationFrame,
} from "./animationFrame.js";

const props = defineProps({
    //开始数字
    start: {
        type: Number,
        required: false,
        default: 0,
    },
    //结束数字
    end: {
        type: Number,
        required: false,
        default: 888,
    },
    //持续时间
    duration: {
        type: Number,
        required: false,
        default: 5000,
    },
    //自动开始
    autoPlay: {
        type: Boolean,
        required: false,
        default: true,
    },
    //小数点
    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;
        },
    },
    //保留几位小数
    default: {
        type: Number,
        required: false,
        default: 0,
    },

});

const isNumber = (val) => {
    return !isNaN(parseFloat(val));
};

// 格式化数据,返回想要展示的数据格式
const formatNumber = (val) => {
    if (props.default) {
        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;
    } else {
        return Math.ceil(val);
    }
};

// 相当于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>


2.引入animationFrame.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 }

3.页面上使用

<countTo :end='end' :autoPlay="true" :duration='3000'  />

4.相关api
start-amount:起始值
end-amount:结束值
duration:持续时长
prefix:前缀
suffix:尾缀
separator:分隔符(千位分隔符)
decimal-separator:小数分隔符
decimals:小数位
autoinit:是否自动执行动画

start():开始
pause():暂停
resume():恢复
reset():重置

5. 插件Vue3 AutoCounter


原网址: 访问
创建于: 2023-07-28 17:50:33
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论