导读:本期聚焦于小伙伴创作的《JS怎么实现前端长列表优化 5种虚拟滚动方案提升万级列表性能》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《JS怎么实现前端长列表优化 5种虚拟滚动方案提升万级列表性能》有用,将其分享出去将是对创作者最好的鼓励。

前端项目中如果存在万级甚至十万级数据的长列表场景,直接将全部数据渲染为DOM节点会导致浏览器渲染压力过大,出现页面卡顿、滚动掉帧等问题。虚拟滚动通过只渲染可视区域内的列表项,动态计算需要展示的数据区间,复用已有的DOM节点,能够大幅降低渲染开销,提升页面性能。

虚拟滚动核心原理

虚拟滚动的核心逻辑可以总结为三个步骤:

  • 计算容器可视区域的高度和每个列表项的高度,得到可视区域能展示的列表项数量
  • 监听容器的滚动事件,根据滚动偏移量计算当前需要展示的数据起始索引和结束索引
  • 只渲染起始索引到结束索引之间的数据,同时调整列表容器的总高度,保证滚动条行为符合预期

方案一:固定高度虚拟滚动(基础版)

适用于所有列表项高度固定的场景,实现逻辑最简单,性能也最优。

实现代码

// 容器元素
const container = document.getElementById('container');
// 列表总数据
const data = Array.from({ length: 10000 }, (_, i) => `列表项 ${i + 1}`);
// 单个列表项高度
const itemHeight = 50;
// 容器可视高度
const containerHeight = container.clientHeight;
// 可视区域可展示的列表项数量
const visibleCount = Math.ceil(containerHeight / itemHeight);
// 列表总高度
const totalHeight = data.length * itemHeight;

// 创建总高度占位容器
const phantom = document.createElement('div');
phantom.style.height = `${totalHeight}px`;
phantom.style.position = 'relative';
container.appendChild(phantom);

// 创建实际渲染内容的容器
const content = document.createElement('div');
content.style.position = 'absolute';
content.style.left = '0';
content.style.top = '0';
content.style.right = '0';
phantom.appendChild(content);

// 初始渲染
function renderContent() {
  const scrollTop = container.scrollTop;
  // 计算起始索引
  const startIndex = Math.floor(scrollTop / itemHeight);
  // 计算结束索引,多渲染2项避免滚动时出现空白
  const endIndex = Math.min(startIndex + visibleCount + 2, data.length);
  // 设置内容容器的偏移量
  content.style.transform = `translateY(${startIndex * itemHeight}px)`;
  // 渲染对应区间的数据
  let html = '';
  for (let i = startIndex; i < endIndex; i++) {
    html += `<div class="list-item" style="height:${itemHeight}px;line-height:${itemHeight}px">${data[i]}</div>`;
  }
  content.innerHTML = html;
}

// 监听滚动事件
container.addEventListener('scroll', renderContent);
// 初始渲染
renderContent();

方案二:动态高度虚拟滚动

适用于列表项高度不固定的场景,需要额外维护每个列表项的高度和位置信息。

实现代码

const container = document.getElementById('container');
const data = Array.from({ length: 10000 }, (_, i) => {
  // 随机生成30-80px的高度
  const height = 30 + Math.random() * 50;
  return { text: `动态高度列表项 ${i + 1}`, height: Math.floor(height) };
});

// 存储每个列表项的位置信息
const positionInfo = [];
let totalHeight = 0;
data.forEach((item, index) => {
  positionInfo.push({
    index,
    height: item.height,
    top: totalHeight,
    bottom: totalHeight + item.height
  });
  totalHeight += item.height;
});

const containerHeight = container.clientHeight;
const phantom = document.createElement('div');
phantom.style.height = `${totalHeight}px`;
phantom.style.position = 'relative';
container.appendChild(phantom);

const content = document.createElement('div');
content.style.position = 'absolute';
content.style.left = '0';
content.style.top = '0';
content.style.right = '0';
phantom.appendChild(content);

// 根据滚动偏移量找到起始索引
function findStartIndex(scrollTop) {
  let left = 0;
  let right = positionInfo.length - 1;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (positionInfo[mid].bottom <= scrollTop) {
      left = mid + 1;
    } else if (positionInfo[mid].top > scrollTop) {
      right = mid - 1;
    } else {
      return mid;
    }
  }
  return left;
}

function renderContent() {
  const scrollTop = container.scrollTop;
  const startIndex = findStartIndex(scrollTop);
  // 计算结束索引
  let endIndex = startIndex;
  let accumulatedHeight = 0;
  while (endIndex < positionInfo.length && accumulatedHeight < containerHeight) {
    accumulatedHeight += positionInfo[endIndex].height;
    endIndex++;
  }
  // 多渲染2项避免空白
  endIndex = Math.min(endIndex + 2, positionInfo.length);
  // 设置偏移量
  content.style.transform = `translateY(${positionInfo[startIndex].top}px)`;
  // 渲染内容
  let html = '';
  for (let i = startIndex; i < endIndex; i++) {
    const item = data[i];
    html += `<div class="list-item" style="height:${item.height}px;line-height:${item.height}px">${item.text}</div>`;
  }
  content.innerHTML = html;
}

container.addEventListener('scroll', renderContent);
renderContent();

方案三:横向虚拟滚动

适用于横向排列的长列表场景,逻辑和纵向虚拟滚动类似,只是计算维度从高度换成宽度。

实现代码

const container = document.getElementById('container');
const data = Array.from({ length: 10000 }, (_, i) => `横向项 ${i + 1}`);
const itemWidth = 120;
const containerWidth = container.clientWidth;
const visibleCount = Math.ceil(containerWidth / itemWidth);
const totalWidth = data.length * itemWidth;

const phantom = document.createElement('div');
phantom.style.width = `${totalWidth}px`;
phantom.style.height = '100%';
phantom.style.position = 'relative';
phantom.style.display = 'flex';
container.appendChild(phantom);

const content = document.createElement('div');
content.style.position = 'absolute';
content.style.top = '0';
content.style.left = '0';
content.style.bottom = '0';
content.style.display = 'flex';
phantom.appendChild(content);

function renderContent() {
  const scrollLeft = container.scrollLeft;
  const startIndex = Math.floor(scrollLeft / itemWidth);
  const endIndex = Math.min(startIndex + visibleCount + 2, data.length);
  content.style.transform = `translateX(${startIndex * itemWidth}px)`;
  let html = '';
  for (let i = startIndex; i < endIndex; i++) {
    html += `<div class="list-item" style="width:${itemWidth}px;flex-shrink:0">${data[i]}</div>`;
  }
  content.innerHTML = html;
}

container.addEventListener('scroll', renderContent);
renderContent();

方案四:DOM节点复用虚拟滚动

在基础固定高度方案上,复用已有的DOM节点,避免频繁创建和销毁DOM,进一步提升性能。

实现代码

const container = document.getElementById('container');
const data = Array.from({ length: 10000 }, (_, i) => `复用DOM项 ${i + 1}`);
const itemHeight = 50;
const containerHeight = container.clientHeight;
const visibleCount = Math.ceil(containerHeight / itemHeight) + 2;
const totalHeight = data.length * itemHeight;

const phantom = document.createElement('div');
phantom.style.height = `${totalHeight}px`;
phantom.style.position = 'relative';
container.appendChild(phantom);

const content = document.createElement('div');
content.style.position = 'absolute';
content.style.left = '0';
content.style.top = '0';
content.style.right = '0';
phantom.appendChild(content);

// 预先创建固定数量的DOM节点
const itemNodes = [];
for (let i = 0; i < visibleCount; i++) {
  const node = document.createElement('div');
  node.className = 'list-item';
  node.style.height = `${itemHeight}px`;
  node.style.lineHeight = `${itemHeight}px`;
  content.appendChild(node);
  itemNodes.push(node);
}

let prevStartIndex = -1;
function renderContent() {
  const scrollTop = container.scrollTop;
  const startIndex = Math.floor(scrollTop / itemHeight);
  // 如果起始索引没变,不需要重新渲染
  if (startIndex === prevStartIndex) return;
  prevStartIndex = startIndex;
  const endIndex = Math.min(startIndex + visibleCount, data.length);
  content.style.transform = `translateY(${startIndex * itemHeight}px)`;
  // 复用DOM节点更新内容
  for (let i = 0; i < itemNodes.length; i++) {
    const dataIndex = startIndex + i;
    if (dataIndex < endIndex) {
      itemNodes[i].textContent = data[dataIndex];
      itemNodes[i].style.display = 'block';
    } else {
      itemNodes[i].style.display = 'none';
    }
  }
}

container.addEventListener('scroll', renderContent);
renderContent();

方案五:结合IntersectionObserver的虚拟滚动

使用IntersectionObserver监听列表项的可见性,替代scroll事件的计算,减少主线程开销。

实现代码

const container = document.getElementById('container');
const data = Array.from({ length: 10000 }, (_, i) => `Observer项 ${i + 1}`);
const itemHeight = 50;
const totalHeight = data.length * itemHeight;

const phantom = document.createElement('div');
phantom.style.height = `${totalHeight}px`;
phantom.style.position = 'relative';
container.appendChild(phantom);

const content = document.createElement('div');
content.style.position = 'absolute';
content.style.left = '0';
content.style.top = '0';
content.style.right = '0';
phantom.appendChild(content);

// 初始渲染第一批数据
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const node = entry.target;
      const index = Number(node.dataset.index);
      // 更新可见项的内容
      node.textContent = data[index];
    }
  });
}, {
  root: container,
  threshold: 0.1
});

// 先创建占位节点
for (let i = 0; i < data.length; i++) {
  const node = document.createElement('div');
  node.className = 'list-item';
  node.style.height = `${itemHeight}px`;
  node.style.lineHeight = `${itemHeight}px`;
  node.dataset.index = i;
  content.appendChild(node);
  observer.observe(node);
}

// 初始设置偏移量
content.style.transform = `translateY(0px)`;

方案选择建议

方案适用场景性能表现
固定高度基础版列表项高度固定最优
动态高度版列表项高度不固定中等,需要维护位置信息
横向虚拟滚动横向排列长列表
DOM复用版需要极致性能的固定高度场景最优
IntersectionObserver版希望减少scroll事件计算开销中等,兼容性需要注意

实际开发中可以根据业务场景选择合适的方案,固定高度场景优先选择DOM复用版或者基础版,动态高度场景选择动态高度方案,横向场景选择横向虚拟滚动方案即可。

virtual_scroll前端性能优化长列表渲染JS虚拟滚动DOM复用修改时间:2026-06-22 07:49:04

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。