神族九帝's blog 神族九帝's blog
首页
  • 神卡套餐 (opens new window)
  • 神族九帝 (opens new window)
  • 网盘资源 (opens new window)
  • 今日热点 (opens new window)
  • 在线PS (opens new window)
  • IT工具 (opens new window)
  • FC游戏 (opens new window)
  • 在线壁纸 (opens new window)
  • 面试突击
  • 复习指导
  • HTML
  • CSS
  • JavaScript
  • 设计模式
  • 浏览器
  • 手写系列
  • Vue
  • Webpack
  • Http
  • 前端优化
  • 项目
  • 面试真题
  • 算法
  • 精选文章
  • 八股文
  • 前端工程化
  • 工作笔记
  • 前端基础建设与架构 30 讲
  • vue2源码学习
  • 剖析vuejs内部运行机制
  • TypeScript 入门实战笔记
  • vue3源码学习
  • 2周刷完100道前端优质面试真题
  • 思维导图
  • npm发包
  • 重学node
  • 前端性能优化方法与实战
  • webpack原理与实战
  • webGl
  • 前端优化
  • Web3
  • React
  • 更多
  • 未来要做的事
  • Stirling-PDF
  • ComfyUI
  • 宝塔面板+青龙面板
  • 安卓手机当服务器使用
  • 京东自动评价代码
  • 搭建x-ui免流服务器(已失效)
  • 海外联盟
  • 好玩的docker
  • 收藏夹
  • 更多
GitHub (opens new window)

神族九帝,永不言弃

首页
  • 神卡套餐 (opens new window)
  • 神族九帝 (opens new window)
  • 网盘资源 (opens new window)
  • 今日热点 (opens new window)
  • 在线PS (opens new window)
  • IT工具 (opens new window)
  • FC游戏 (opens new window)
  • 在线壁纸 (opens new window)
  • 面试突击
  • 复习指导
  • HTML
  • CSS
  • JavaScript
  • 设计模式
  • 浏览器
  • 手写系列
  • Vue
  • Webpack
  • Http
  • 前端优化
  • 项目
  • 面试真题
  • 算法
  • 精选文章
  • 八股文
  • 前端工程化
  • 工作笔记
  • 前端基础建设与架构 30 讲
  • vue2源码学习
  • 剖析vuejs内部运行机制
  • TypeScript 入门实战笔记
  • vue3源码学习
  • 2周刷完100道前端优质面试真题
  • 思维导图
  • npm发包
  • 重学node
  • 前端性能优化方法与实战
  • webpack原理与实战
  • webGl
  • 前端优化
  • Web3
  • React
  • 更多
  • 未来要做的事
  • Stirling-PDF
  • ComfyUI
  • 宝塔面板+青龙面板
  • 安卓手机当服务器使用
  • 京东自动评价代码
  • 搭建x-ui免流服务器(已失效)
  • 海外联盟
  • 好玩的docker
  • 收藏夹
  • 更多
GitHub (opens new window)
  • 工作笔记

    • 国际化多语言一键替换,在线翻译
    • 虚拟列表
      • 最简单的虚拟列表
      • 带有上下缓存的虚拟列表
      • 不定高度的虚拟列表
    • vscode插件开发
    • Code Review
  • 前端基础建设与架构 30 讲

  • vue2源码学习

  • 剖析vuejs内部运行机制

  • TypeScript 入门实战笔记

  • vue3源码学习

  • 2周刷完100道前端优质面试真题

  • 思维导图

  • npm发包

  • 重学node

  • 前端性能优化方法与实战

  • webpack原理与实战

  • webGl

  • 前端优化

  • Web3

  • React

  • 更多

  • 笔记
  • 工作笔记
wu529778790
2023-08-24

虚拟列表

# 最简单的虚拟列表

https://wallpaper.shenzjd.com/#/vitualList/simple (opens new window)

<template>
  <div class="virtual" @scroll="onScroll">
    <div
      class="virtual-phantom"
      :style="{ height: `${data.length * itemHeight}px` }"></div>
    <div
      class="virtual-list"
      :style="{ transform: `translateY(${startTop}px)` }">
      <div
        v-for="item in virtualList"
        :key="item"
        class="item"
        :style="{ height: itemHeight + 'px' }">
        {{ item }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from "vue";
const data = ref(Array.from({ length: 100 }, (_, i) => i + 1));

const start = ref(0);
const count = ref(6);
const end = computed(() => start.value + count.value);
const itemHeight = 200;

const virtualList = computed(() => {
  return data.value.slice(start.value, end.value);
});

const startTop = ref(0);
const onScroll = (e) => {
  const scrollTop = e.target.scrollTop;
  start.value = Math.floor(scrollTop / itemHeight);
  // 向下取整(比较好理解)
  startTop.value =
    scrollTop % itemHeight
      ? Math.floor(scrollTop / itemHeight) * itemHeight
      : scrollTop;
  // 通用写法
  // startTop.value = scrollTop - (scrollTop % itemHeight);
};
</script>

<style lang="scss" scoped>
.virtual {
  height: 100%;
  overflow: auto;
  position: relative;
  .virtual-phantom {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
  }
  .virtual-list {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    .item {
      background-color: #eee;
      display: flex;
      justify-content: center;
      align-items: center;
      border-bottom: 1px solid #ccc;
      box-sizing: border-box;
    }
  }
}
</style>

# 带有上下缓存的虚拟列表

https://wallpaper.shenzjd.com/#/vitualList/buffer (opens new window)

<template>
  <div class="virtual-wapper" @scroll="onScroll">
    <div
      class="virtual-background"
      :style="{ height: totalHeight + 'px' }"></div>
    <div
      class="virtual-list"
      :style="{
        top: -(topBufferLength * itemHeight) + 'px',
        bottom: -(bottomBufferLength * itemHeight) + 'px',
        transform: `translate3d(0, ${startOffset}px, 0)`,
      }">
      <div
        v-for="item in virtualList"
        :key="item"
        class="item"
        :style="{ height: itemHeight + 'px' }">
        {{ item }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onBeforeMount } from "vue";
const data = ref(Array.from({ length: 100 }, (_, i) => i + 1));

const itemHeight = 100;

const totalHeight = computed(() => data.value.length * itemHeight);
// 起始索引
const startIndex = ref(0);
// 显示行数
const count = ref(0);
// 结束索引
const endIndex = ref(0);
// 缓冲行数
const buffer = ref(2);
// 缓冲起始索引
const bufferStartIndex = computed(() => {
  return Math.max(0, startIndex.value - buffer.value);
});
// 缓冲结束索引
const bufferEndIndex = computed(() => {
  return Math.min(data.value.length, endIndex.value + buffer.value);
});
// 顶部缓冲个数
const topBufferLength = computed(() => {
  return startIndex.value - Math.max(0, startIndex.value - buffer.value);
});
// 底部缓冲个数
const bottomBufferLength = computed(() => {
  return (
    Math.min(endIndex.value + buffer.value, data.value.length) - endIndex.value
  );
});

const virtualList = computed(() => {
  return data.value.slice(bufferStartIndex.value, bufferEndIndex.value);
});

onBeforeMount(() => {
  const { innerHeight } = window;
  count.value = Math.ceil(innerHeight / itemHeight);
  endIndex.value = startIndex.value + count.value;
});

const startOffset = ref(0);
const onScroll = (event) => {
  const scrollTop = event.target.scrollTop;
  startIndex.value = Math.floor(scrollTop / itemHeight);
  endIndex.value = Math.min(startIndex.value + count.value, data.value.length);
  startOffset.value = scrollTop - (scrollTop % itemHeight);
};
</script>

<style lang="scss" scoped>
.virtual-wapper {
  height: 100%;
  position: relative;
  overflow: auto;
  .virtual-background {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
  }
  .virtual-list {
    position: absolute;
    left: 0;
    right: 0;
    .item {
      background-color: #eee;
      display: flex;
      justify-content: center;
      align-items: center;
      border-bottom: 1px solid #ccc;
      box-sizing: border-box;
    }
  }
}
</style>

# 不定高度的虚拟列表

https://wallpaper.shenzjd.com/#/vitualList/variableHeight (opens new window)

<template>
  <div ref="wapper" class="virtual-wapper" @scroll="onScroll">
    <div
      class="virtual-background"
      :style="{ height: totalHeight + 'px' }"></div>
    <div
      class="virtual-list"
      :style="{
        transform: `translate3d(0, ${startOffset}px, 0)`,
      }">
      <div
        v-for="item in virtualList"
        :key="item"
        class="item"
        :style="{ height: item.height + 'px' }">
        第{{ item.id }}个,高度{{ item.height }}px
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from "vue";

/**
 * Generates a random number between the given minimum and maximum values (inclusive).
 *
 * @param {number} min - The minimum value of the range.
 * @param {number} max - The maximum value of the range.
 * @return {number} - A random number between the given minimum and maximum values.
 */
const generateRandomNumber = (min, max) => {
  return Math.floor(Math.random() * (max - min + 1) + min);
};

const data = ref(
  Array.from({ length: 10 }, (_, i) => {
    return {
      id: i + 1,
      height: generateRandomNumber(100, 700),
    };
  })
);

// 预估高度, 用已知的高度来计算平均高度
const estimatedHeight = computed(() => {
  if (!cacheHeight.get(endIndex.value)) {
    return 100;
  }
  return cacheHeight.get(endIndex.value) / (endIndex.value + 1);
});

// 缓存的实际总高度
const cacheHeight = new Map();

// 起始索引
const startIndex = ref(0);

const wapper = ref(null);

// 结束索引
const endIndex = ref(1);

// 设置缓存
const setCacheHeight = (i) => {
  if (!cacheHeight.has(i)) {
    cacheHeight.set(
      i,
      i === 0
        ? data.value[i].height
        : cacheHeight.get(i - 1) + data.value[i].height
    );
  }
};

// 当前页面不够展示一页就要加载更多
const checkEndIndex = () => {
  while (
    cacheHeight.get(endIndex.value - 1) - cacheHeight.get(startIndex.value) <=
      wapper.value.offsetHeight &&
    endIndex.value < data.value.length - 1
  ) {
    endIndex.value++;
    setCacheHeight(endIndex.value);
  }
  while (
    cacheHeight.get(endIndex.value - 1) - cacheHeight.get(startIndex.value) >
    wapper.value.offsetHeight
  ) {
    endIndex.value--;
  }
};

onMounted(() => {
  for (let i = 0; i <= endIndex.value; i++) {
    setCacheHeight(i);
  }
  checkEndIndex();
});

const virtualList = computed(() => {
  // 计算显示行数
  return data.value.slice(startIndex.value, endIndex.value + 1);
});

const totalHeight = computed(() => {
  return (
    (data.value.length - 1 - endIndex.value) * estimatedHeight.value +
    cacheHeight.get(endIndex.value)
  );
});

const startOffset = ref(0);
const onScroll = (event) => {
  const { scrollTop } = event.target;
  // 先判断是否在最顶部的上面还是下面
  if (scrollTop > cacheHeight.get(startIndex.value)) {
    let i = 0;
    while (scrollTop > cacheHeight.get(startIndex.value + i)) {
      i++;
    }
    startIndex.value = startIndex.value + i;
    startOffset.value = scrollTop;
  }
  if (
    startIndex.value > 0 &&
    scrollTop < cacheHeight.get(startIndex.value - 1)
  ) {
    let i = 0;
    while (scrollTop < cacheHeight.get(startIndex.value - 1 - i)) {
      i++;
    }
    startIndex.value = startIndex.value - i;
    startOffset.value = scrollTop - data.value[startIndex.value].height;
  }
  console.log(`在索引为${startIndex.value}的元素上`);
  checkEndIndex();
};
</script>

<style lang="scss" scoped>
.virtual-wapper {
  height: 100%;
  position: relative;
  overflow: auto;
  .virtual-background {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
  }
  .virtual-list {
    position: absolute;
    left: 0;
    right: 0;
    .item {
      background-color: #eee;
      display: flex;
      justify-content: center;
      align-items: center;
      border-bottom: 1px solid #ccc;
      box-sizing: border-box;
    }
  }
}
</style>
编辑 (opens new window)
上次更新: 2025/03/17, 12:21:00
国际化多语言一键替换,在线翻译
vscode插件开发

← 国际化多语言一键替换,在线翻译 vscode插件开发→

最近更新
01
Code Review
10-14
02
ComfyUI
10-11
03
vscode插件开发
08-24
更多文章>
Power by vuepress | Copyright © 2015-2025 神族九帝
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×