vue3数字滚动动画


在这里插入图片描述

1.新建组件vue-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: 2021,
  },
  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) => {
  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,
  () => {
    console.log(111);
    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'  />
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的Vue数字滚动抽奖的实现示例: 1. 在template中定义一个数字滚动的容器: ```html <template> <div class="number-roller"> <div class="number" v-bind:style="numberStyle">{{ number }}</div> </div> </template> ``` 2. 在data中定义数字滚动的数值和样式: ```javascript export default { data() { return { number: 0, numberStyle: { transform: 'translateY(0px)', transition: 'transform 0.3s ease-out' } } } } ``` 3. 在computed中计算数字滚动的样式和值: ```javascript computed: { numberStyle() { return { transform: `translateY(${-this.number * 30}px)`, transition: 'transform 0.3s ease-out' } } } ``` 这里假设数字是在0到9之间滚动,每个数字之间的间距为30像素。当数字改变时,计算属性会重新计算数字滚动的样式。 4. 在methods中添加一个"startRolling"方法,以触发数字滚动动画: ```javascript methods: { startRolling() { let count = 0; let interval = setInterval(() => { this.number = Math.floor(Math.random() * 10); // 随机生成一个数字 count++; if (count > 10) { // 滚动10次后停止滚动 clearInterval(interval); this.$emit('rolling-complete', this.number); // 触发抽奖完成事件,传递中奖号码 } }, 300); } } ``` 这里使用setInterval方法每隔0.3秒随机生成一个数字滚动10次后停止滚动,并通过$emit方法触发抽奖完成事件,将中奖号码传递给父组件。 5. 在父组件中使用"NumberRoller"组件,并监听抽奖完成事件: ```html <template> <div> <button @click="startLottery">开始抽奖</button> <number-roller v-if="rolling" @rolling-complete="handleRollingComplete"></number-roller> </div> </template> ``` 6. 在父组件中定义"startLottery"方法,以开始抽奖: ```javascript methods: { startLottery() { this.rolling = true; }, handleRollingComplete(number) { this.rolling = false; alert(`恭喜您中奖了,中奖号码为${number}`); } } ``` 这里使用"rolling"变量控制数字滚动组件的显示和隐藏,当抽奖完成时,显示中奖号码的提示框。 这样就完成了一个简单的Vue数字滚动抽奖的实现。你可以根据实际需求进行更加丰富的自定义和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值