以下案例模拟10万条数据
一次性渲染
LCP 1.68s
<script>
// 获取元素
let ul= document.querySelector('.bottom ul')
// 渲染10万个li
for(let i=0;i<100000;i++){
let li = document.createElement('li')
li.innerText = i
ul.appendChild(li)
}
</script>
分页
用户只能看到当前页的数据,待用户翻页时,再动态加载下一页的数据。这种方法可以极大减少一次性渲染的数据量。
懒加载
LCP 0.09s
每次只渲染一部分(比如10条),等剩余部分滚到可见区域,再渲染一部分。有点像分页
clientHeight
:视口的高度;scrollTop
:元素内容被卷去的头部高度值;scrollHeight
: 元素内容总高度包括不可见元素
缺点
当滚动到数据底部,页面仍然存在大量的dom
节点
<script>
// 获取内容列表
let ul = document.querySelector('.bottom ul')
// 获取容器元素
let bottom = document.querySelector('.bottom')
// 已经加载的项目
let count = 0;
// 每次加载的项目
let loadCount = 7;
// 总共的项目
let sum = 100000;
// 懒加载
function lazyLoad() {
// 判断是否加载完成
if (count + loadCount >= sum) {
// 如果剩余项目数少于loadCount,则只加载剩余的项目
let remainingItems = sum - count;
for (let i = 0; i < remainingItems; i++) {
let li = document.createElement('li');
li.innerHTML = count + 1 + i;
ul.appendChild(li);
count++;
}
console.log("加载完成");
return;
}
// 每次加载的项目
for (let i = 0; i < loadCount; i++) {
let li = document.createElement('li');
li.innerHTML = count + 1;
count++;
ul.appendChild(li);
}
}
// 设置节流函数
function throttle(fn, delay) {
let timer = null;
return () => {
if (timer) {
return;
};
timer = setTimeout(() => {
fn();
timer = null;
}, delay)
}
}
// 监听滚动事件
bottom.addEventListener('scroll', throttle(() => {
console.log("scroll触发");
if (bottom.scrollTop + bottom.clientHeight >= ul.scrollHeight - 1) {
lazyLoad();
}
}, 500))
// 初始化,加载页面数据
lazyLoad();
</script>
虚拟列表
LCP 0.11s
核心思想是只渲染用户可见区域的元素,减少浏览器的渲染开销, 解决懒加载下渲染的dom节点过多的问题
<script>
// 获取容器和列表元素
const listScroll = document.querySelector('.bottom')
const list = document.querySelector('.list')
// 源数据
const dataSource = []
// 渲染数据=> 通过定义首位index截取源数据
let renderData = []
// item的高度
const itemHeight = 100
// listScroll容器能够显示的最大数量
//可视区容器最大显示的元素个数 向上取整,保证可视区域占满
const maxCount = Math.ceil(listScroll.clientHeight / itemHeight) + 4
// 开始位置索引
let startIndex = 0
// 记录到的位置索引
let pointerIndex = 0
// 结束位置索引
let endIndex = maxCount
// 源数据获取
function GetData() {
for (let i = 0; i < 100000; i++) {
dataSource.push(i)
}
}
// 计算结束位置索引
function ComputePointerPosition() {
const end = startIndex + maxCount
// 注意判断是否超出数据源长度
endIndex = dataSource[end] ? end : dataSource.length
}
// 每一次的渲染数据
function GetRenderData() {
//区间左闭右开
renderData = dataSource.slice(startIndex, endIndex)
}
// 渲染
function Render() {
// 计算开始与结束位置
ComputePointerPosition(startIndex, endIndex)
// 获得渲染数据
GetRenderData()
// 将截取的渲染数据生成动态的list元素,填充到list内容元素
list.innerHTML = renderData.map(item => `<li>${item}</li>`).join('')
}
// 监听滚动事件
listScroll.addEventListener('scroll', ScrollHandle)
// 监听listOut滚动事件
function ScrollHandle() {
// 更新开始位置索引:滚动的距离 / 每个元素的高度
startIndex = Math.floor(listScroll.scrollTop / itemHeight)
// 一致不做渲染
if (pointerIndex === startIndex) return
// 更新位置,重新渲染
pointerIndex = startIndex
// 更新位置,重新渲染
Render()
// 测试发现每次向下滚动一个元素,列表会向上移动一个元素的位置,所以增加transform属性,使列表位置向下移动一个元素的位置
// startIndex表示已经上移到的元素的个数,itemHeight表示每个元素的高度
list.style.transform = `translateY(${startIndex * itemHeight}px)`
}
//获取源数据
GetData()
Render()
</script>