npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@holyer-lib/virtual-list

v0.1.1

Published

A virtual-list component for Vue2

Readme

虚拟滚动组件 HiVirtualList 实现解析与实战经验

背景:在构建大型前端组件库时,高性能列表渲染是常见需求。本文基于 Vue 2.6 + Less + Rollup 技术栈,深入剖析一个生产级虚拟滚动组件 HiVirtualList 的设计思路、关键实现细节及踩坑经验。


一、为什么需要虚拟滚动?

当列表项数量超过 1000+ 时:

  • 全量渲染导致 DOM 节点爆炸(内存占用高)
  • 滚动卡顿(浏览器重排/重绘压力大)
  • 初始化慢(Vue 需创建大量 VNode)

虚拟滚动核心思想

只渲染视口内可见项 + 上下缓冲区,其余用空白占位


二、HiVirtualList 核心实现思路

🧱 1. 结构设计(三段式布局)

<div class="hi-virtual-list">
  <!-- 顶部空白:模拟已滚出视口的内容 -->
  <div :style="{ height: topPlaceholderHeight }"></div>
  
  <!-- 可见区域:实际渲染的 DOM -->
  <div v-for="item in visibleItems" :key="..." />
  
  <!-- 底部空白:模拟未进入视口的内容 -->
  <div :style="{ height: bottomPlaceholderHeight }"></div>
</div>

优势

  • 总高度 = items.length * itemHeight(保持滚动条比例正确)
  • 实际 DOM 节点数 ≈ visibleCount(通常 20~50 个)

⚙️ 2. 关键计算逻辑(响应式驱动)

| 计算属性 | 作用 | |---------|------| | startIndex | 当前视口顶部对应的原始数据索引 | | endIndex | 当前视口底部对应的原始数据索引 | | visibleItems | items.slice(startIndex, endIndex) | | topPlaceholderHeight | startIndex * itemHeight | | bottomPlaceholderHeight | (total - endIndex) * itemHeight |

💡 所有计算由 scrollTop 驱动,天然响应式


🔁 3. 滚动同步机制

handleScroll(e) {
  this.scrollTop = e.target.scrollTop; // 更新状态
  // computed 自动重新计算 visibleItems
}
  • 用户滚动 → 触发 scroll 事件 → 更新 scrollTop → Vue 自动更新 DOM
  • 无手动 DOM 操作,完全依赖 Vue 响应式系统

🔑 4. Key 生成策略(健壮性保障)

getNodeKey(item, index) {
  if (有效 nodeKey && item[nodeKey] != null) {
    return item[nodeKey];
  }
  return index; // fallback 到原始索引
}

解决两大痛点

  1. 用户未传 nodeKey 时自动降级
  2. 部分数据缺失 key 字段时仍能渲染

🎯 5. 滚动定位 API(智能路由)

scrollTo(target) {
  if (启用了 nodeKey) {
    // 按 keyValue 查找(调用 getNodeKey 对齐)
  } else {
    // 按 index 查找
  }
}

关键:查找逻辑与渲染 key 生成严格一致,避免定位错乱


三、开发者踩坑经验

❌ 坑 1:will-change: scroll-position 导致白屏

现象
启用 scroll-behavior: smooth 后,scrollToTop() 出现白屏。

根因
will-change 触发图层提升,与 CSS 平滑滚动冲突,导致渲染时机错乱。

解决方案

/* 移除 will-change */
.hi-virtual-list {
  /* will-change: scroll-position; ← 删除 */
}

经验:虚拟滚动组件慎用 will-change,性能收益远小于稳定性风险。


❌ 坑 2:scrollTo 与渲染 key 不一致

现象
部分数据无 id 字段,scrollTo(5) 无法定位到第 5 项。

根因
scrollTo 直接读取 item[nodeKey],而渲染时已 fallback 到 index

解决方案

// scrollTo 内部调用 getNodeKey 进行匹配
index = items.findIndex((item, i) => 
  getNodeKey(item, i) === target
);

经验:任何依赖 key 的逻辑必须复用 getNodeKey,保证行为统一。


❌ 坑 3:缓冲区(buffer)不足

现象
快速滚动时偶现白屏。

解决方案

buffer: {
  type: Number,
  default: 50 // 从 20 提升到 50
}

经验buffer 是防白屏的第一道防线,宁可多渲染,不可留空白。


❌ 坑 4:平滑滚动(smooth)的陷阱

结论
虚拟滚动组件不应默认启用 scroll-behavior: smooth

原因

  • CSS 动画与 Vue 响应式更新存在帧级不同步
  • 主流组件库(Element Plus、Ant Design)均未启用
  • 用户更关注滚动流畅度而非动画效果

经验:性能 > 动画,稳定 > 酷炫。


四、最佳实践用法

场景 1:基础用法(按索引滚动)

<template>
  <HiVirtualList
    :items="list"
    :item-height="60"
    height="400px"
    ref="virtualList"
  >
    <template #default="{ item, index }">
      <div>{{ index }} - {{ item.name }}</div>
    </template>
  </HiVirtualList>
</template>

<script>
export default {
  methods: {
    jumpToTop() {
      this.$refs.virtualList.scrollToTop();
    },
    jumpTo5th() {
      this.$refs.virtualList.scrollTo(5); // 滚动到第 5 项
    }
  }
};
</script>

场景 2:使用唯一 Key

<HiVirtualList
  :items="users"
  node-key="uid" <!-- 使用 uid 字段作为 key -->
  :item-height="50"
  height="300"
  ref="list"
/>
// 滚动到 uid = 'user_123' 的用户
this.$refs.list.scrollTo('user_123');

五、性能优化建议

| 优化点 | 说明 | |-------|------| | 固定 itemHeight | 必须为常量,动态高度需另寻方案 | | 避免 slot 内复杂逻辑 | 渲染函数越简单越好 | | 图片懒加载 | 防止滚动时触发大量网络请求 | | 禁用 transition | 避免布局抖动 |


六、总结

HiVirtualList 的设计哲学:

简单、稳定、可预测

  • 最少的代码 解决 最核心的问题
  • 不追求花哨功能,专注滚动性能与渲染正确性
  • 防御性编程:对边界情况(如缺失 key、无效参数)友好提示

📌 最后建议
如果你的项目需要支持动态高度、树形结构等复杂场景,建议评估 vue-virtual-scroll-list 等成熟方案,避免重复造轮子。但对于 90% 的固定高度列表场景,HiVirtualList 已足够健壮高效。