vue实现数字逐位滚动效果

231 阅读1分钟

样式自行调整即可,我写的比较簡陋,主要就是两个css属性writing-mode: 控制元素排列的主轴,text-orientation": 垂直模式下文字排列方向,将0-9垂直排列后用transform: translateY(-xx%) 控制显示哪个数字。 功能上主要是兩個函數,numAnimation動畫效果和reLoop重新加載
详情见4noth1ng/vue-number-scroll: vue实现数字滚动组件 (github.com)

<template>
  <div class="wrapper">
    <div class="container" ref="container" v-if="reloop">
      <div
        class="item"
        v-for="(item, index) in targetArr"
        :key="index"
        ref="target"
      >
        0123456789
      </div>
    </div>

    <button class="btn" @click="reLoop">重新加載</button>
  </div>
</template>

<script>
export default {
  name: "numberScroll",

  props: {
    // 目标数字
    target: {
      type: String,
      required: true,
    },
    // 容器宽度
    width: {
      type: Number,
      required: true,
    },
    // 容器高度
    height: {
      type: Number,
      required: true,
    },
    // 容器字体大小 建议与height一致 否则样式会乱
    fontSize: {
      type: Number,
      required: true,
    },
  },

  data() {
    return {
      targetArr: [],
      timer: null,
      reloop: true,
    };
  },

  methods: {
    numAnimation() {
      for (const str of this.target) {
        this.targetArr.push(parseInt(str));
      }
      this.$nextTick(() => {
        // 修改容器样式
        const { container } = this.$refs;
        container.style.width = `${this.width}px`;
        container.style.height = `${this.height}px`;
        container.style.fontSize = `${this.fontSize}px`;
        const { target } = this.$refs;
        const size = this.targetArr.length;
        // 修改 数字 translateY 以及其 样式
        this.targetArr.forEach((targetNum, index) => {
          const width = (100 / size).toFixed(6);
          const dom = target[index];
          dom.style.width = `${width}%`;
          dom.style.height = `${this.height * 10}px`;
          dom.style.lineHeight = `${(width / 100) * this.width}px`;
          dom.style.transition = `all linear ${0.5 + index * 0.2}s`;
          for (let i = 0; i <= targetNum; i++) {
            const idx = -i * 10;
            this.timer = setTimeout(() => {
              dom.style.transform = `translateY(${idx}%)`;
            }, 0);
          }
        });
      });
    },

    reLoop() {
      // 重新加载组件
      this.reloop = false;
      this.$nextTick(() => {
        this.reloop = true;
        this.targetArr = [];
        this.numAnimation();
      });
    },
  },

  mounted() {
    this.numAnimation();
  },

  unmounted() {
    if (this.timer) {
      clearTimeout(this.timer);
    }
  },
};
</script>

<style scoped lang="scss">
.wrapper {
  width: 1200px;

  height: 1000px;

  margin: 0 auto;

  .container {
    margin: 200px;

    font-size: 38px;

    font-weight: 500;

    display: flex;

    width: 130px;

    height: 38px;

    overflow: hidden;

    .item {
      height: 380px;

      text-orientation: upright;

      writing-mode: vertical-lr;

      transition: all linear 1s;

      text-align: center;

      &:hover {
        cursor: default;
      }
    }
  }

  .btn {
    width: 230px;

    height: 30px;

    font-size: 20px;
  }
}
</style>

</style>
<template>
  <div class="wrapper">
    <button class="btn" @click="reLoop">重新加載</button>
    <div class="test-container" v-if="reloop">
      <ul
        class="test-list"
        v-for="(item, index) in targetArr"
        :key="index"
        ref="list"
      >
        <li>0</li>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  name: "numberScroll",

  props: {
    // 目标数字
    target: {
      type: String,
      required: true,
    },
  },

  data() {
    return {
      targetArr: [],
      timer: null,
      reloop: true,
    };
  },

  methods: {
    listAnimation() {
      for (const str of this.target) {
        this.targetArr.push(parseInt(str));
      }
      this.$nextTick(() => {
        const { list } = this.$refs;
        // 修改 数字 translateY 以及其 样式
        this.targetArr.forEach((targetNum, index) => {
          const dom = list[index];
          dom.style.transition = `all linear ${0.5 + index * 0.2}s`;
          for (let i = 0; i <= targetNum; i++) {
            const idx = -i * 100;
            this.timer = setTimeout(() => {
              dom.style.transform = `translateY(${idx}%)`;
            }, 0);
          }
        });
      });
    },

    reLoop() {
      // 重新加载组件
      this.reloop = false;
      this.$nextTick(() => {
        this.reloop = true;
        this.targetArr = [];
        this.listAnimation();
      });
    },
  },

  mounted() {
    this.listAnimation();
  },

  unmounted() {
    if (this.timer) {
      clearTimeout(this.timer);
    }
  },
};
</script>

<style scoped lang="scss">
.wrapper {
  width: 1200px;

  height: 1000px;

  margin: 0 auto;

  .btn {
    width: 230px;
    height: 30px;
    font-size: 20px;
    margin-bottom: 100px;
  }
  .test-container {
    width: 100px;
    height: 37px;
    display: flex;
    overflow: hidden;
    .test-list {
      padding: 0;
      margin: 0;
      list-style: none;
      text-align: center;
      font-size: 28px;
      li {
        height: 37px;
      }
    }
  }
}
</style>