原生不支持虚拟滚动,因其渲染由系统控件接管,DOM不可干预;bootstrap-select等方案通过自定义实现虚拟滚动,手写需撑高容器、监听滚动、计算索引、局部更新。

原生 <select></select> 不支持虚拟滚动,硬塞 10 万条 <option></option> 会卡死浏览器——这不是配置问题,是 DOM 渲染机制决定的。必须换方案。
为什么原生 select 无法开启虚拟滚动
浏览器对 <select></select> 的渲染完全由系统控件或 UA 样式接管,DOM 层面不可干预。你无法监听其滚动、劫持渲染、动态替换 <option></option> 节点。所有“给 select 加 overflow”“用 CSS 模拟滚动”的尝试,最终都只是在伪造滚动行为,实际仍加载全部选项。
- 设置
size="20"只是让下拉变成固定行数的列表框,不减少 DOM 节点 -
overflow: auto对原生<select></select>无效(除非用size触发列表框模式,但依然全量渲染) - 任何试图用 JavaScript 动态增删
<option></option>来模拟滚动的方案,都会因频繁 DOM 操作导致掉帧甚至卡死
bootstrap-select 的 virtualScroll 是怎么工作的
它根本没用原生 <select></select>,而是用 <div> + <code><ul></ul> + <li> 完全重写了下拉菜单 UI,并在内部实现了一套滚动位置映射逻辑:只生成当前可视区域的 <li>,滚动时通过 scrollTop 计算起始索引,再用 slice() 截取数据子集重新渲染。
- 启用条件:
data-virtual-scroll="true"或data-virtual-scroll="600"(≥600 项自动触发) - 必须配合
data-size(如data-size="8")设定菜单高度,否则无法计算可视行数 - 搜索功能
data-live-search="true"强烈建议开启,否则用户根本没法在 10 万条里定位 - 注意版本:v1.13.0+ 才支持,v1.14.0-beta3 起默认阈值为 600
不用第三方库时怎么手写最小可行虚拟滚动
核心就四步:撑高容器、监听滚动、算索引、局部更新。不需要框架,纯 JS + HTML 就能跑:
立即学习“前端免费学习笔记(深入)”;
<div class="virtual-select" style="height: 200px; overflow-y: auto;"> <div class="full-height-box" style="height: 1000000px;"></div> <ul class="visible-list"></ul> </div>
-
full-height-box高度 = 单项高度 × 总数据条数(比如 32px × 100000 = 3.2e6px),用来撑开滚动空间 - 监听
virtual-select的scroll事件,读取scrollTop - 起始索引 =
Math.floor(scrollTop / 32),结束索引 = 起始 +Math.ceil(200 / 32)≈ 7 - 用
data.slice(start, end)获取子数组,清空.visible-list后批量插入<li>
注意:单项高度必须是固定值(不能用 em/%),否则计算会漂移;滚动事件要防抖,避免高频重绘。
容易被忽略的兼容性坑
移动端 Safari 和旧版 Chrome 对 scrollTop 的精度处理不一致,尤其在快速滑动时可能跳帧或索引错位。
- 不要依赖
scrollHeight做计算,它在某些安卓 WebView 里返回 0 - 避免在
scroll回调里直接操作 DOM,改用requestIdleCallback或节流到 16ms 一帧 - 如果数据含分组(
<optgroup></optgroup>),虚拟滚动需额外维护分组头位置,原生 bootstrap-select 的virtualScroll不支持分组渲染 - 键盘导航(方向键、Home/End)需要手动绑定事件并同步滚动位置,否则可访问性归零
真正的大数据选择场景,从来不是“怎么让 select 滚起来”,而是“什么时候该放弃 select”。虚拟滚动只是过渡方案,最终往往要换成 searchable combobox 或异步搜索 + 分页加载。



















