Vue 实现虚拟列表

Vue实现虚拟列表无限滚动

要点:
每一项的高度
可视范围内可以展示的数量
列表在页面中距离网页顶部的位置

结构部分

<template>
  <div class="virtual-scrolling">
    <div class="list" ref="refList" @scroll="scroll">
      <div
        class="item"
        v-for="(item, index) in renderList"
        :key="index"
        :style="{ transform: `translateY(${translateY}px)` }"
      >
        <div class="info">
          <div class="name">{{ item.name }}</div>
          <div class="date">{{ item.date }}</div>
        </div>
        <div class="desc">{{ item.desc }}</div>
      </div>
    </div>
  </div>
</template>

js部分

const throttle = (fn, delay) => {
  let tag = false;
  return function (...args) {
    if (tag) return;
    tag = true;
    setTimeout(() => {
      fn.apply(this, args);
      tag = false;
    }, delay);
  };
};

export default {
  name: "VirtualScrolling",
  data() {
    return {
      list: [],
      renderList: [],
      itemH: 133,
      count: 0,
      startIndex: 0,
      translateY: 0,
    };
  },
  methods: {
    init() {
      const clientH = document.documentElement.clientHeight;
      this.count = Math.ceil(clientH / this.itemH) + 3;
      this.renderList = this.list.splice(this.startIndex, this.count);
    },
    getPosts() {
      return new Promise((resolve, reject) => {
        this.$axios.get("/api/posts").then((res) => {
          if (res.status === 200) {
            this.list = res.data.data;
            resolve();
          } else {
            reject("数据加载失败");
          }
        });
      });
    },
    scrollHandle() {
      const scrollTop = this.$refs.refList.scrollTop;
      const start = Math.floor(scrollTop / this.itemH);
      // 1
      if (this.startIndex != start) {
        // 2
        const listOffsetTop = scrollTop - (scrollTop % this.itemH);
        // 3
        this.renderList = this.list.slice(start, this.startIndex + this.count);
        // 4
        this.translateY = listOffsetTop;
      }
      this.startIndex = start;
    },
    scroll() {
      throttle(this.scrollHandle(), 50);
    },
  },
  created() {
    this.getPosts().then(() => {
      this.init();
    });
  },
};

css部分

.virtual-scrolling {
  .list {
    height: 100vh;
    overflow-y: scroll;
    .item {
      height: 133px;
      box-sizing: border-box;
      padding: 10px;
      border-bottom: 1px solid #e3e3e3;
      .info {
        display: flex;
        align-items: center;
        .name {
          width: 40px;
          height: 40px;
          line-height: 40px;
          text-align: center;
          border-radius: 40px;
          background-color: #bbb;
          color: #fff;
        }
        .date {
          flex: 1;
          margin-left: 10px;
          color: #666;
          text-align: right;
        }
      }
      .desc {
        margin-top: 10px;
      }
    }
  }
}

代码核心方法 scrollHandle (1、2、3、4标记处)

  1. 起始点不同于上一次,重新渲染列表;
  2. 需要获得一个可以被itemHeight整除的数来作为item的偏移量;
  3. 截取需要渲染的那一部分;
  4. 每一项的位移量 translateY。

最后,使用Vue3完成同样的效果(结构和css部分一样,在此忽略)

import axios from "axios";
import { onMounted, ref } from "vue";

const throttle = function (fn: void, delay: number) {
  let tag = false;
  return function (...args: []) {
    if (tag) return;
    tag = true;
    setTimeout(function () {
      (fn as any).apply(fn, args);
      tag = false;
    }, delay);
  };
};

const refList = ref<HTMLElement | any>();
const list = ref<Array<any>>([]);
const renderList = ref<Array<any>>([]);
const itemH = ref<number>(133);
const count = ref<number>(0);
const startIndex = ref<number>(0);
const translateY = ref<number>(0);

const getPosts = async () => {
  const res = await axios.get("/api/posts");
  list.value = res.data.data;
  init();
};

const scrollHandle = () => {
  const scrollTop = refList.value.scrollTop;
  const start = Math.floor(scrollTop / itemH.value);
  if (startIndex.value != start) {
    const listOffsetTop = scrollTop - (scrollTop % itemH.value);
    renderList.value = list.value.slice(start, startIndex.value + count.value);
    translateY.value = listOffsetTop;
  }
  startIndex.value = start;
};
const scroll = () => {
  throttle(scrollHandle(), 50);
};

const init = () => {
  const clientH = document.documentElement.clientHeight;
  count.value = Math.ceil(clientH / itemH.value) + 3;
  renderList.value = list.value.splice(startIndex.value, count.value);
};

onMounted(() => {
  getPosts();
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值