万有斥力 发表于 2024-12-14 23:14:49

这是一个vue3 + scss的数字滚动效果

先容:
        当数字变化时,只改变变化的数字位,其余的不变,可以递增、递减、骤变、负数也可以,但是样式要根据详细的项目需求去改;
效果1、增加数字:
https://i-blog.csdnimg.cn/direct/014eaf09d798406d9c6d8f9abc688337.gif
效果2、淘汰数字:
https://i-blog.csdnimg.cn/direct/bf80a72762fb4237b7834bf34de8ef03.gif
使用方法:
<template>
<AnimatNumber :data="data" />
</template>

<script setup>
// 引入动画
import AnimatNumber from "./components/AnimatNumber.vue";

const data = ref(0);

setInterval(() => {
data.value -= 30;
}, 2000);
</script>

<style lang="scss">

</style>
组件代码(vue3):
<template>
<div class="num-wrap">
    <div v-for="(item, index) in computedData" :key="index" class="num-item">
      <div class="num-inner" ref="numInnerRef">
      <div class="prev">{{ item.newValue }}</div>
      <div class="current">{{ item.oldValue }}</div>
      <div class="next">{{ item.oldValue }}</div>
      </div>
    </div>
</div>
</template>

<script setup>
// 数字滚动效果
import { onBeforeUnmount, watch, ref, nextTick } from "vue";

const props = defineProps({
// 传进来的数据number、string的number都可以
data: {
    type: ,
    default: 999
},
// 动画持续时间number、string的number都可以最低1000ms
duration: {
    type: ,
    default: 500
},
// 基本的高度 所有的动画移动距离都是和这个有关的,确保这个值和css的$height一样,否则有问题
baseHeight: {
    type: Number,
    default: 50
}
});

const numInnerRef = ref();

// raf演示器
const setTimeoutPolyfill = (func, delay) => {
let startTime = Date.now();
let rafId;

function animationFrameCallback() {
    const currentTime = Date.now();
    const timeElapsed = currentTime - startTime;

    if (timeElapsed >= delay) {
      func();
    } else {
      rafId = requestAnimationFrame(animationFrameCallback);
    }
}
rafId = requestAnimationFrame(animationFrameCallback);
// 返回一个取消函数
return () => cancelAnimationFrame(rafId);
};

/*
推演公式
    新          旧
    1001->   1000
    1002->   1001
    1003->   1002
    1004->   1003
    1005->   1004
*/

const newArr = ref([]);
const oldArr = ref([]);
const computedData = ref(
props.data
    .toString()
    .split("")
    .map((item, index) => ({ index, oldValue: item, newValue: item }))
);
const lock = ref(false);
// 延时器
const timer = ref({
timerOne: null,
timerTwo: null
});

watch(
() => props.data,
(newVal, oldVal) => {
    if (`${newVal}`.length !== `${oldVal}`.length) {
      lock.value = false;
    }
    if (!lock.value) {
      computedData.value = props.data
      .toString()
      .split("")
      .map((item, index) => ({ index, oldValue: item, newValue: item }));
      lock.value = true;
    }
    newArr.value = newVal
      .toString()
      .split("")
      .map((item, index) => ({ index, value: item }));

    oldArr.value = oldVal
      .toString()
      .split("")
      .map((item, index) => ({ index, value: item }));
    /*
      如果newArr的长度大于于oldArr的长度,则需要给oldArr从前面增加newArr.length - oldArr.length的长度的{ index, oldValue: '-', newValue: newValueItem },
      同时更新oldArr没有新增的index
    */

    // 新值和老值差
    const differLength = newArr.value.length - oldArr.value.length;
    if (newArr.value.length > oldArr.value.length) {
      for (let i = 0; i < differLength; i++) {
      oldArr.value.unshift({ index: i, value: "-" });
      }
      // 重新设置index
      oldArr.value.forEach((item, index) => (item.index = index));
    }

    // 改变的数字的索引集合
    const indexArr = [];
    newArr.value.forEach(item => {
      if (item.value !== oldArr.value.value) {
      indexArr.push(item.index);
      }
    });
    nextTick(() => {
      indexArr.forEach(diffIndex => {
      numInnerRef.value.children.innerHTML =
          newArr.value.value;
      numInnerRef.value.children.animate(
          [{ top: `${-props.baseHeight}px` }, { top: 0 }],
          {
            duration: props.duration,
            fill: "forwards"
          }
      );
      numInnerRef.value.children.animate(
          [{ top: "0" }, { top: `${props.baseHeight}px` }],
          {
            duration: props.duration,
            fill: "forwards"
          }
      );
      timer.value.timerOne = setTimeoutPolyfill(() => {
          numInnerRef.value.children.innerHTML =
            oldArr.value.value;
          timer.value.timerTwo = setTimeoutPolyfill(() => {
            numInnerRef.value.children.innerHTML =
            newArr.value.value;
          }, props.duration);
          numInnerRef.value[
            diffIndex
          ].children.style.top = `${-props.baseHeight}px`;
      }, props.duration);
      });
    });
},
{ deep: true }
);

// 卸载
onBeforeUnmount(() => {
timer.value.timerOne && timer.value.timerOne();
timer.value.timerTwo && timer.value.timerTwo();
});
</script>

<style lang="scss" scoped>
$width: 50px;
$height: 50px;
.num-wrap {
margin-top: 200px;
display: flex;
gap: 10px;
.num-item {
    width: $width;
    height: $height;
    border: 1px solid #000;
    border-radius: 8px;
    font-size: 20px;
    font-weight: 600;
    position: relative;
    overflow: hidden;
    color: #0dfbff;
    background: rgba(0, 13, 23, 0.5);
    .num-inner {
      position: relative;
      width: $width;
      height: $height;
    }
    .prev,
    .current,
    .next {
      width: $width;
      height: $height;
      text-align: center;
      line-height: $width;
      position: absolute;
    }
    .prev {
      top: -$height;
    }
    .current {
      top: 0;
    }
    .next {
      top: $height;
    }
}
}
</style>

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 这是一个vue3 + scss的数字滚动效果