本文是对 Virtual Scrolling for Billions of Rows — Techniques from HighTable 的精华提炼。原作者 Severo Ochoa,项目地址 hyparam/hightable。强烈建议阅读原文,包含交互式 demo 和可视化图解。
问题
在浏览器里显示一个两行两列的表格是 HTML 101。但当数据量到十亿行时,一切都会崩:内存撑不住、DOM 节点爆炸、滚动条精度不够。
HighTable 是一个 React 组件,用五种递进式技术解决了这个问题 — 而且全程使用原生 HTML 元素,没有 fake scrollbar,没有 canvas 渲染。
五个技巧
1. 懒加载 (Lazy Loading)
问题: 10B 行 × 100 bytes/行 = 1TB,不可能全部加载。
方案: 只加载当前可见的 ~30 行。通过 DataFrame 抽象层按需 fetch + 缓存,新数据就绪时触发 resolve 事件重新渲染。
const rowStart = Math.floor(firstVisiblePixel / rowHeight)
const rowEnd = Math.ceil(lastVisiblePixel / rowHeight)
效果: 1TB 数据只需 ~3KB 内存。
2. 表格切片 (Table Slice)
问题: 100 万行 = 100 万个 <tr>,Chrome 建议不超过 300 个 DOM 节点。
方案: 在 viewport 和 table 之间插入一个 canvas div(不是 <canvas> 元素),设置为全表高度以撑出正确的滚动条。table 只渲染可见行,用 position: absolute + 动态 top 定位。
canvas.style.height = `${numRows * rowHeight}px`
table.style.top = `${scrollTop - (scrollTop % rowHeight)}px`
效果: 不管多少行,DOM 节点恒定 ~30 个。
3. 无限像素 (Infinite Pixels)
问题: 浏览器对元素高度有上限(Firefox ~17M px)。33px 行高 → 最多 50 万行。
方案: 设置 canvas 最大高度(8M px),超出后用缩放因子映射滚动位置到全表位置。
downscaleFactor = (fullTableHeight - clientHeight) / (maxCanvasHeight - clientHeight)
firstVisibleRow = Math.floor((scrollTop * downscaleFactor) / rowHeight)
代价: 滚动精度下降。10B 行时,滚动 1px ≈ 跳过 ~7300 万行。某些行通过滚动条不可达。
4. 像素级精确滚动 (Pixel-precise Scroll)
问题: 技巧 3 让每次滚动都是”全局跳转”,无法逐行浏览。
方案: 双模式滚动 — 维护 { globalAnchor, localOffset } 状态。小幅滚动(鼠标滚轮)= local,累加 offset;大幅滚动(拖动滚动条)= global,重置 anchor。
if (Math.abs(delta) > threshold) {
state.localOffset = 0 // global jump
state.globalAnchor = scrollTop
} else {
state.localOffset += delta // local fine scroll
}
效果: 行高 30px 时,2 万亿行以内保证 1px 精度。64 万亿行以内每行可达。
5. 两步随机访问 (Two-step Random Access)
问题: 键盘导航(↓ / Ctrl+End)可能跳转到不在 DOM 中的行。
方案: 分离垂直和水平滚动。先更新状态 → 程序化垂直滚动 → 渲染目标行到 DOM → 水平滚动到目标列 → focus。用 flag 防止程序化滚动触发多余的水平滚动。
关键:程序化滚动必须用 behavior: 'instant',避免 smooth 产生的中间状态冲突。
效果: 键盘可达任意单元格,即使十亿行。
核心洞察
这五个技巧是递进式的,每一层解决上一层引入的新问题:
| 技巧 | 解决 | 引入 |
|---|---|---|
| 懒加载 | 内存不够 | — |
| 表格切片 | DOM 节点太多 | — |
| 无限像素 | 浏览器高度上限 | 滚动精度丢失 |
| 双模式滚动 | 精度丢失 | 状态复杂度 |
| 两步随机访问 | 键盘导航断裂 | — |
最值得注意的设计决策:全程使用原生 HTML 元素。没有 fake scrollbar,没有 <canvas> 渲染,依赖 Web 平台的 overflow-y: auto、position: sticky、scrollIntoView。这让组件天然保留了浏览器的无障碍支持(WAI Grid Pattern + tabindex roving)。
链接
- 原文(含交互式 demo): rednegra.net/blog/20260212-virtual-scroll
- HighTable Demo: hyparam.github.io/demos/hightable
- GitHub: hyparam/hightable
相关文章
加入讨论
分享您的想法,与其他读者交流。评论通过 GitHub 管理,确保安全可靠。
请遵守我们的 评论政策,共同维护良好的讨论环境