大屏表格实现无限滚动效果

实现效果

hmgif3

实现思路

  1. 首先固定最外层的高度,并且设置超出高度后隐藏
  2. 设置每一行的高度为固定35PX,默认显示10行,所以最外层高度就是 35 * 10 + 表头的高度
  3. 遍历时克隆一份表格数据,用于视差效果显示
  4. 设置滚动动画,让表格行所在的父元素 tableTr 不断往上移动,当移动到末尾数据时,此时定义的移动距离 position 刚好时总高度的一半
  5. 然后重置 position 的距离为0,因为我们克隆了一份数据,此时便不会有闪跳的情况,给人的感觉就是继续往上滚动,从而实现无限滚动的效果

完整代码

<template>
  <div class="table-box">
    <!--表头-->
    <div class="table-th">
      <div style="width: 60px">排名</div>
      <div>区域</div>
      <div>总服务人数</div>
      <div>2023年</div>
      <div>2024年</div>
    </div>
    <!--表格数据-->
    <div class="table-tr" ref="tableTr" @mouseenter="pause" @mouseleave="resume">
      <!--克隆一份数据-->
      <div
        class="tr-item"
        v-for="(item, index) in [...tableData, ...tableData]"
        :key="index"
        :class="{ 'even-background': index < 15 ? index % 2 !== 0 : index % 2 === 0 }"
      >
        <div class="index-item">
          <div v-if="item.ranking <= 3" class="tab-index index-bg">
            {{ item.ranking }}
          </div>
          <div v-else class="tab-index">
            {{ item.ranking }}
          </div>
        </div>
        <div>{{ item.province }}</div>
        <div>{{ item.totalServiceNumber }}</div>
        <div>{{ item.yearNumber2 }}</div>
        <div>{{ item.yearNumber3 }}</div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'demo2',
  data() {
    return {
      tableData: [],
      position: 0,
      animationId: null,
    }
  },
  mounted() {
    this.getTableData()
    this.$nextTick(() => {
      this.animation()
    })
  },
  methods: {
    /**
     * 暂停动画。
     * 此方法通过取消动画帧请求来暂停动画的执行。
     * 当鼠标移入组件时调用,以暂停动画的播放。
     */
    pause() {
      cancelAnimationFrame(this.animationId) // 取消当前的动画帧请求
      this.animationId = null // 清空动画帧请求的ID
      this.isPaused = true // 标记动画为暂停状态
    },
    /**
     * 恢复动画。
     * 如果动画当前处于暂停状态,此方法会重新启动动画。
     * 当鼠标移出组件时调用,以恢复动画的播放。
     */
    resume() {
      if (this.isPaused) {
        // 当动画处于暂停状态时
        this.animation() // 重新调用动画函数
        this.isPaused = false // 将动画状态设置为播放
      }
    },
    /**
     * 启动一个动画,用于滚动表格中的行。
     * 此函数没有参数和返回值,因为它直接操作DOM并启动一个循环动画。
     * 动画效果是通过改变行的垂直偏移来实现的。
     */
    animation() {
      // 获取将要进行动画的ul元素
      let tableTr = this.$refs.tableTr
      // 获取ul元素的高度,用于计算动画的终点
      let tableTrHeight = tableTr.offsetHeight
      /**
       * 动画循环函数。
       * 不断调整元素的垂直偏移,创造向下滚动的效果。
       * 当元素偏移超过其高度的一半时,重置偏移值,实现循环滚动。
       */
      const animate = () => {
        this.position -= 2 // 每次循环减少的偏移量,控制动画速度
        if (this.position <= -(tableTrHeight / 2)) {
          this.position = 0 // 当偏移量小于等于负的一半高度时,重置偏移量,准备下一次循环
        }
        tableTr.style.transform = `translateY(${this.position}px)` // 应用偏移量到元素的transform属性上,实现动画效果
        this.animationId = requestAnimationFrame(animate) // 请求下一个动画帧继续执行此函数,保持动画运行
      }
      animate() // 初始化动画
    },
    getTableData() {
      const generatedData = []
      const provinces = [
        '北京',
        '上海',
        '广东',
        '江苏',
        '浙江',
        '山东',
        '河南',
        '湖南',
        '湖北',
        '四川',
        '福建',
        '安徽',
        '陕西',
        '重庆',
        '云南',
      ]
      const randomServiceNumber = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
      for (let i = 0; i < 15; i++) {
        const province = provinces[Math.floor(Math.random() * provinces.length)]
        const totalServiceNumber = randomServiceNumber(1000, 10000).toString()
        const yearNumber2 = randomServiceNumber(500, 5000).toString()
        const yearNumber3 = randomServiceNumber(1000, 8000).toString()

        generatedData.push({
          ranking: i + 1,
          province,
          totalServiceNumber,
          yearNumber2,
          yearNumber3,
        })
      }

      this.tableData = generatedData
    },
  },
}
</script>

<style scoped lang="scss">
.table-box {
  background-color: #000;
  color: white;
  // 最外层父元素必须要定高
  width: 500px;
  height: 385px;
  overflow: hidden;
}

.table-th {
  display: flex;
  align-items: center;
  justify-content: space-around;
  background-color: #1a8dec;
  font-weight: 100;
  font-size: 14px;
  padding: 2px 0;
  height: 35px;
  line-height: 35px;
  position: sticky;
  z-index: 2;

  & > div {
    width: 120px;
    text-align: center;
  }
}

.table-tr {
  display: flex;
  flex-direction: column;

  .tr-item {
    display: flex;
    align-items: center;
    justify-content: space-around;
    font-weight: 100;
    font-size: 12px;
    height: 35px; // 每一行的高度也必须固定,用户计算滚动距离
    line-height: 35px;

    & > div {
      width: 120px;
      text-align: center;
    }

    .index-item {
      width: 60px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
  }

  //.tr-item:nth-child(odd) {
  //  background-color: #322c5f;
  //}
}

.even-background {
  background-color: #322c5f;
}

.tab-index {
  width: 16px;
  height: 16px;
  line-height: 16px;

  text-align: center;
  border-radius: 50%;
  background-color: #4666ff;
}

.index-bg {
  background-color: #f43737;
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值