---
description: "数据大到全量渲染会卡时的形状：固定行高的滚动容器里，只渲染视口内（及边缘附近）的行，其余高度用 spacer 撑住，滚动时按 scrollTop 重算切片。几千行想一屏滚到底、不分页也不卡，就是它"
---

# 虚拟表格（大列表窗口化渲染）

数据大到全量渲染会卡时的形状：固定行高的滚动容器里，只渲染视口内（及边缘附近）的行，其余高度用 spacer 撑住，滚动时按 `scrollTop` 重算切片。几千行想一屏滚到底、不分页也不卡，就是它。

<iframe src="/skills/dashboard/assets/demo-virtual-table.html"
        style="width:100%;height:660px;border:1px solid #8884;border-radius:10px"
        loading="lazy" title="形状 demo：虚拟表格"></iframe>

试这几下：

- 拖滚动条快速滚 —— 顶部计数实时变：5000 行里 DOM 始终只有约 30 个行节点
- 搜索「研发」或点**姓名 / 金额**表头 —— 过滤后总高重算、自动回顶，窗口化照常工作
- 点任意行高亮，滚走再滚回来 —— 高亮还在（状态挂在稳定 id 上，不挂在 DOM 上）
- 勾选**关闭虚拟化** —— 5000 行全量进 DOM，警示条报出本次渲染耗时，滚动明显变卡

## 规矩

- **行高必须固定**（`ROW_HEIGHT`）：`scrollTop → 起始下标` 的窗口化算术全靠它；再配少量 `OVERSCAN`（demo 上下各 8 行）消除快速滚动的白屏
- **上下 spacer 行撑起总滚动高度**，表头 `position: sticky` 不随行滚走；过滤后行数变了，spacer 跟着重算
- **总行数 + 当前渲染区间常驻显示**，「只渲染了一小片」随时可验证
- **不为窗口化引新依赖**：蓝本模板和 demo 都手写窗口化（几行算术）；真实版遇到行高可变、横向虚拟化等复杂需求，再换 `@tanstack/react-virtual`
- 真实数据接法：从 `Repository` 一次取一大页（或流式）喂进数组，窗口化只管渲染层

> 蓝本：`add-virtual-table.md`[^add-virtual-table]（open-dashboard @ `aa9815f`，MIT，Invariants 已消化进上面「规矩」）

??? abstract "demo 源码：`assets/demo-virtual-table.html`（自包含、未压缩）"

    ```html
    --8<-- "skills/dashboard/assets/demo-virtual-table.html"
    ```

[^add-virtual-table]: [`add-virtual-table.md`](https://github.com/ahpxex/open-dashboard/blob/aa9815f426e9520841c4aa15a341bfd9f866091f/.claude/skills/add-component/references/add-virtual-table.md)，2026-07-28 读。
