一个简单的虚拟滚动

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

先上效果

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 让刚才滚出视口的那个渲染第一项的容器平移下来,用来渲染第二项。

相关推荐
拉不动的猪1 小时前
vue与react的简单问答
前端·javascript·面试
污斑兔1 小时前
如何在CSS中创建从左上角到右下角的渐变边框
前端
星空寻流年1 小时前
css之定位学习
前端·css·学习
旭久2 小时前
react+antd封装一个可回车自定义option的select并且与某些内容相互禁用
前端·javascript·react.js
是纽扣也是烤奶2 小时前
关于React Redux
前端
阿丽塔~2 小时前
React 函数组件间怎么进行通信?
前端·javascript·react.js
冴羽3 小时前
SvelteKit 最新中文文档教程(17)—— 仅服务端模块和快照
前端·javascript·svelte
uhakadotcom3 小时前
Langflow:打造AI应用的强大工具
前端·面试·github
前端小张同学3 小时前
AI编程-cursor无限使用, 还有谁不会🎁🎁🎁??
前端·cursor