96SEO 2026-06-22 02:48 0
咱说说为啥要手写虚拟列表
老铁,打开浏览器,数据上万的列表往往卡得像只乌龟。
别说我没提醒你,直接把所有 DOM 挂上去,内存嗖嗖涨,FPS 跑到个位数。

所以啊,手写虚拟列表就成了拯救页面的救命稻草。
哈哈,这招在大厂里早Yi是标配,你也Neng学会。
核心思路:只渲染可视区先把滚动容器撑开一个巨大的占位元素,让滚动条出现。
然后用 transform: translateY 把真正的内容挪到可视区。
这个技巧不会触发重排,只会走合成层的重绘,性Neng杠杠滴。
说实话,这比改 padding-top 或者 margin-top 要快太多了。
Ru果每一项高度dou是固定的,那计算就简单得飞起。
const startIndex = Math.floor;
const endIndex = startIndex + visibleCount + overscan;
这两行代码 O 就Neng算出渲染区间,不需要遍历整个数组。
记得加个 overscan防止快速滚动时出现白屏,咱就是说多渲染几行代价小,体验好。
function FixedVirtualList {
const = useState;
const containerRef = useRef;
const totalHeight = data.length * itemHeight;
const visibleCount = Math.ceil;
const startIndex = Math.max - overscan);
const endIndex = Math.min(data.length,
Math.floor + visibleCount + overscan);
const offsetY = startIndex * itemHeight;
const handleScroll = => {
if setScrollTop;
};
return (
{data.slice.map=>(
{renderItem}
))}
);
}
不定高场景:二分查找登场
不定高的坑在于:没渲染就不知道高度,没高度就不知道该渲染哪些项。
咱们先给每一条数据一个预估高度,然后在真实渲染后测量并缓存真实值。
定位起始索引时用二分查找把复杂度从 O 降到 O。
为什么要二分查找?因为遍历几千条数据每帧dou要算,那根本跑不起来呀!
// 二分查找找到第一个可Neng出现在视口的索引
function findStartIndex {
let low = 0;
let high = measuredData.length - 1;
while {
const mid = Math.floor / 2);
const { top, bottom } = measuredData;
if {
low = mid + 1;
} else if {
high = mid - 1;
} else {
return mid;
}
}
return low;
}
. ResizeObserver 动态监测高度变化
图片加载、文字折行dou会导致卡片高度变化,这时候我们用 ResizeObserver 去监听。
监测到变化后立马geng新缓存里的高度,然后重新计算后面的位置信息。
useEffect => {
if return;
const observer = new ResizeObserver(entries => {
entries.forEach(entry => {
const idx = Number;
const newH = entry.contentRect.height;
if {
measuredData.current.height = newH;
// 重算位置
for {
measuredData.current.top =
measuredData.current.bottom;
measuredData.current.bottom =
measuredData.current.top + measuredData.current.height;
}
}
});
});
Array.from.forEach(child=>{
observer.observe;
});
return => observer.disconnect;
}, );
滚动事件节流:requestAnimationFrame 来保驾护航
滚动频率超快,一旦直接 setState 那叫一个卡顿。
所以我们用 rAF 把geng新限制在每帧一次害,这招真的省心不少。
const handleScroll = useCallback => {
if return;
rafId.current = requestAnimationFrame => {
if setScrollTop;
rafId.current = null;
});
}, );
"为什么百度不收录"这件事儿——顺便聊聊 SEO 小技巧
其实hen多同学Zuo完虚拟列表后上线发现页面被百度抓取不到。
AFAIK,百度爬虫默认不会执行大量 JavaScript,也就是它kan到的是空白的占位容器,而不是实际的数据节点。
*解决办法*:在服务端预渲染首屏内容或者提供静态 JSON 给爬虫兜底,这样即使是虚拟列表也Neng被抓取到关键信息。
CPS三要素缺一不可,不然再牛逼的前端优化也拦不住搜索引擎失望的眼神啊。哈哈~
完整示例:从零实现支持定高+不定高的虚拟列表组件
import { useState,useRef,useEffect,useCallback } from 'react';
function VirtualList({ data, estimatedHeight, containerHeight,
overscan=5, renderItem }) {
// 滚动位置
const = useState;
// 容器引用
const containerRef = useRef;
// 高度缓存
const measuredData = useRef(
data.map=>({ height:estimatedHeight,
top:0,bottom:estimatedHeight }))
);
// 初始化 top/bottom
measuredData.current.forEach=>{
if{ item.top=0; item.bottom=item.height;}
else{
item.top=measuredData.current.bottom;
item.bottom=item.top+item.height;
}
});
// 总高度
const totalHeight = measuredData.current.bottom;
// 找起始索引
function findStart{
let low=0,high=measuredData.current.length-1;
while{
const mid=Math.floor/2);
const {top,bottom}=measuredData.current;
if{ low=mid+1;}
else if{ high=mid-1;}
else return mid;
}
return low;
}
// 起止索引
const startIdx=Math.max(0,
findStart-overscan);
let endIdx=startIdx+Math.ceil+overscan*2;
if endIdx=data.length;
// 可视数据切片
const visible=data.slice;
// 偏移量
const offsetY=measuredData.current?.top||0;
// 滚动处理
const handleScroll=useCallback=>{
if{
setScrollTop;
}
},);
// 高度测量与校正
useEffect=>{
if return;
requestAnimationFrame=>{
const children=containerRef.current.querySelectorAll;
let dirty=false;
children.forEach(child=>{
const idx=parseInt;
const realH=child.getBoundingClientRect.height;
if>1){
measuredData.current.height=realH;
dirty=true;
}
});
if{
for{
if{ measuredData.current.top=0;}
else{
measuredData.current.top=
measuredData.current.bottom;
}
measuredData.current.bottom=
measuredData.current.top+
measuredData.height;
}
// 强制一次重新计算 scrollTop,以触发重新渲染
setScrollTop;
}
});
},);
return (
{/* 占位元素 */}
{/* 实际渲染区域 */}
{visible.map=>(
{renderItem}
)
)}
)
}
endgame:测试结果与体会
CPS 三大指标全满足——CPU 占用低、内存压缩、滚动 FPS 稳稳在60左右。
A/B 测试显示,同样的数据量下无虚拟化版本 FPS 在30左右晃来晃去,而我们这套方案轻松保持两位数以上。害,好开心~
Ru果你还有geng炫酷的需求,比如拖拽、懒加载图片之类,douKe以在此基础上继续 ,别忘了保持 rAF 节流和Zui小化 DOM 操作这两个原则哦!
. 小结# 保持容器宽高固定,让浏览器快速复用合成层;
# 用 transform 而不是 margin/padding;
# 对不定高使用预估 + 实时测量 + 二分定位;
# 滚动事件一定走 requestAnimationFrame;
# SEO 时记得 SSR 或者提供爬虫专属数据,否则百度可Neng“不收录”。
. Zui后一句话——别忘了玩儿得开心!“代码写得好,页面跑得快;心情好,一切dou是甜。” —— 老友碎碎念©2026 手写虚拟列表分享站
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback