一个简单的虚拟滚动

一个刚入行没多久的菜比前端写的很简单的虚拟滚动,欢迎大佬们批评指正

先上效果

html部分

html 复制代码
    <div class="container">
      <div class="scroll-container" id="scroll-container">
        <div class="placeholder"></div>
        <div
          class="scroll-content"
          id="scroll-content"
          style="height: 1000000px"
        >
        </div>
      </div>
    </div>

css部分

css 复制代码
    .container {
      width: 100%;
      height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .placeholder {
      width: 100%;
    }
    .scroll-container {
      width: 300px;
      height: 500px;
      border: 1px solid #ccc;
      overflow-y: scroll;
      position: relative;
    }

    .scroll-content {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
    }

scroll-container为列表的容器 scroll-content 为列表的可视部分,绝对定位展示在容器的最上层(表面) placeholder为一个实际上撑起列表高度的div(实际)

js部分

渲染前准备
js 复制代码
    const scrollContainer = document.getElementById("scroll-container");
    const scrollContent = document.getElementById("scroll-content");
    const placeholder = document.querySelector(".placeholder");
    const itemHeight = 50; // 每个项目的高度
    const totalItems = 100; // 总项目数
    const bufferItems = 5; // 缓冲项目数
    const visibleItems = Math.ceil(scrollContainer.clientHeight / itemHeight); // 可见的项目数
    const renderedItems = visibleItems + bufferItems; // 渲染的项目数
    placeholder.style.height = `${totalItems * itemHeight}px`;//给placeholder设宽度为总项目数*高度
渲染函数部分
js 复制代码
  // 生成虚拟滚动内容
  function renderItems(startIndex) {  //startIndex为应该渲染的第一项的索引
    startIndex = Math.max(0, Math.min(startIndex, totalItems - visibleItems));//边界检查 Math.min()是为了限制索引的不要超过最大项目数-可见项目数,Math.max防止索引小于0
       const fragment = document.createDocumentFragment();
    let i = startIndex;
    for (i; i < startIndex + renderedItems; i++) { //开始渲染 起始为startIndex,到startIndex + renderedItems终止
      if (i > totalItems - 1) break; //超过最大项数停止
      const item = document.createElement("div");
      item.className = "item";
      item.style.height = `${itemHeight}px`;
      item.style.lineHeight = `${itemHeight}px`;
      item.style.boxSizing = "border-box";
      item.style.padding = "0 10px";
      item.style.borderBottom = "1px solid #eee";
      item.textContent = `Item ${i + 1}`;
      fragment.appendChild(item);
    }
      scrollContent.style.height = `${(i - startIndex) * itemHeight}px`;//重设可见区域高度为实际渲染了多少项的高度
    scrollContent.innerHTML = "";
    scrollContent.appendChild(fragment);//重新渲染可见区域
  }

Math.min(startIndex, totalItems - visibleItems)是为了限制索引的不要超过最大项目数-可见项目数,比如一共100项,可见10项,拉到最下面也就从90项开始渲染,不能再多了。 Math.max是防止索引小于0,比如一共5项,可见项目10,如果只有Math.min就-5了,加个Math.max防止其小于0。 重设scrollContent.style.height是防止可见区域的高度大于剩余项的高度 比如还剩10项,renderedItems始终是15,如果不加的话高度就为15*50px了。

渲染部分
js 复制代码
   // 初始渲染
   renderItems(0);

   // 使用requestAnimationFrame优化滚动性能
   let isScrolling = false;
   scrollContainer.addEventListener("scroll", () => {
     if (!isScrolling) {
       window.requestAnimationFrame(() => {
         const scrollTop = scrollContainer.scrollTop;//滚动了多少
         const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight));//重新计算应渲染的下一项的索引
         renderItems(startIndex);
         scrollContent.style.transform = `translateY(${startIndex * itemHeight}px)`;//将可视区域向下移动 每往上滚了一个itemHeight就往下移一个itemHeight
         isScrolling = false;
       });
       isScrolling = true;
     }
   });

采用transform来实现滚动效果,原理大概如下 startIndex最开始是0 当scrollTop为50时意味着往上滚动了整整一项,那么这时该从第二项开始渲染了,startIndex变为1,同时translate 50px 让刚才滚出视口的那个渲染第一项的容器平移下来,用来渲染第二项。

相关推荐
Allen_LVyingbo2 分钟前
病历生成与质控编码的工程化范式研究:从模型驱动到系统治理的范式转变
前端·javascript·算法·前端框架·知识图谱·健康医疗·easyui
rgeshfgreh7 分钟前
Python函数全解析:定义、参数与作用域
前端·数据库·python
Serendipity-Solitude14 分钟前
使用HTML创建井字棋
前端·html
Aotman_1 小时前
JS 按照数组顺序对对象进行排序
开发语言·前端·javascript·vue.js·ui·ecmascript
Hi_kenyon9 小时前
VUE3套用组件库快速开发(以Element Plus为例)二
开发语言·前端·javascript·vue.js
起名时在学Aiifox9 小时前
Vue 3 响应式缓存策略:从页面状态追踪到智能数据管理
前端·vue.js·缓存
李剑一10 小时前
uni-app实现本地MQTT连接
前端·trae
EndingCoder10 小时前
Any、Unknown 和 Void:特殊类型的用法
前端·javascript·typescript