为什么要对长列表进行优化
在处理大量数据时,如果不进行优化,会导致以下问题:
- 性能问题:在浏览器中渲染大量数据会导致性能问题,特别是在移动设备上更为明显。如果数据量很大,那么浏览器可能会变得非常慢或甚至崩溃。
- 用户体验问题:用户很难处理大量数据,如果列表中有很多条目,用户可能需要进行大量滚动才能找到他们想要的内容。这会导致用户感到疲劳和不满意。
性能问题的原因
性能问题通常是由于大量数据的处理和渲染引起的。在前端中,当我们需要渲染大量数据时,浏览器必须完成以下任务:
- 创建 DOM 元素:浏览器需要为每个数据项创建 DOM 元素,这可能是非常昂贵的。
- 布局和绘制:浏览器需要计算每个元素的位置,并将它们绘制在屏幕上。这也是一个昂贵的操作。
- 数据处理:前端代码必须对数据进行处理,以便渲染它们。这可能涉及大量的计算和逻辑运算,特别是在复杂的列表中。
- 网络请求:在从服务器加载数据时,网络请求可能会导致延迟和性能问题。
所有这些操作都需要浏览器进行处理,而浏览器的性能受到多种因素的影响,如处理器速度、内存大小和可用带宽等。如果数据量非常大,这些操作可能会变得非常耗时和耗能,从而导致性能问题。本文主要探求与 DOM 相关的优化思路。
为什么说 DOM 元素的开销非常昂贵呢? 是因为操作 DOM 元素会触发浏览器的重绘机制
优化方案
虚拟列表是一种用于优化长列表渲染性能的技术。它通过只渲染视窗内可见的元素,而不是所有的元素来提高渲染效率。当用户滚动列表时,虚拟列表会根据滚动位置动态地渲染新的元素,并删除不再可见的元素,从而减少DOM操作和重新渲染的次数,从而提高性能。
以下是虚拟列表的实现方式,可以优化页面性能:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Virtualized List</title>
<style>
#viewport {
height: 400px;
overflow-y: scroll;
position: relative;
}
#list {
position: absolute;
top: 0;
left: 0;
right: 0;
}
#scrollbar {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 10px;
background-color: #ccc;
opacity: 0.5;
border-radius: 5px;
}
</style>
</head>
<body>
<div id="viewport">
<div id="list"></div>
<div id="scrollbar"></div>
</div>
</body>
<script>
// 虚拟列表的数据源
const data = Array.from({ length: 100000 }, (_, index) => index + 1);
// 单个列表项的高度
const ITEM_HEIGHT = 30;
// 可视区域的高度
const VIEWPORT_HEIGHT = 400;
// 可视区域中同时显示的列表项数量
const VISIBLE_ITEMS_COUNT = Math.ceil(VIEWPORT_HEIGHT / ITEM_HEIGHT);
// 虚拟滚动条的高度
const SCROLLBAR_HEIGHT = VIEWPORT_HEIGHT * VIEWPORT_HEIGHT / data.length;
// 生成虚拟列表的DOM结构
const listEl = document.getElementById('list');
listEl.style.height = `${ITEM_HEIGHT * data.length}px`;
listEl.innerHTML = data.map((item) => `<div>${item}</div>`).join('');
// 生成虚拟滚动条的DOM结构
const scrollbarEl = document.getElementById('scrollbar');
scrollbarEl.style.height = `${SCROLLBAR_HEIGHT}px`;
// 使用防抖技术,监听滚动事件,更新列表和滚动条的位置
const viewportEl = document.getElementById('viewport');
// 隐藏滚动条
viewportEl.addEventListener('mouseleave', () => {
scrollbarEl.style.opacity = 0.5;
});
// 根据需要显示的列表项,更新DOM元素的内容
viewportEl.addEventListener('scroll', () => {
const scrollTop = viewportEl.scrollTop;
const startIndex = Math.floor(scrollTop / ITEM_HEIGHT);
const endIndex = Math.min(startIndex + VISIBLE_ITEMS_COUNT, data.length);
listEl.style.transform = `translateY(${startIndex * ITEM_HEIGHT}px)`;
scrollbarEl.style.top = `${scrollTop * VIEWPORT_HEIGHT / data.length}px`;
scrollbarEl.style.opacity = 1;
for (let i = startIndex; i < endIndex; i++) {
const itemEl = listEl.children[i];
if (!itemEl) {
break;
}
itemEl.innerText = data[i];
}
});
</script>
</html>
Vue3推荐类库
Vue 3中有一个名为<virtual-scroller>的组件,它可以用于渲染大量数据的长列表,并且能够在性能和渲染效果之间找到一个平衡点。
<virtual-scroller>可以实现以下优化:
- 仅渲染可见区域内的元素,可以大大减少DOM操作,提高渲染性能;
- 支持缓存元素,避免重复创建DOM节点,提高渲染性能;
- 可以动态计算元素高度,避免静态设置固定高度时出现的问题;
- 支持动态调整列表高度,可以自适应容器大小变化。
以下是一个使用<virtual-scroller>组件的示例代码:
<template>
<virtual-scroller
:items="items"
:item-height="itemHeight"
:total-items="totalItems"
:overscan-count="overscanCount"
:key-field="keyField"
@range-change="onRangeChange"
>
<template #default="{ item }">
<div class="list-item">
{{ item }}
</div>
</template>
</virtual-scroller>
</template>
<script>
import { ref } from 'vue';
import VirtualScroller from 'vue3-virtual-scroller';
export default {
components: {
VirtualScroller,
},
setup() {
const items = ref(Array.from({ length: 10000 }, (_, i) => i + 1));
const itemHeight = ref(50);
const totalItems = ref(items.value.length);
const overscanCount = ref(10);
const keyField = ref('id');
function onRangeChange(range) {
console.log('Visible range:', range.startIndex, '-', range.endIndex);
}
return {
items,
itemHeight,
totalItems,
overscanCount,
keyField,
onRangeChange,
};
},
};
在上面的代码中,我们首先引入了vue3-virtual-scroller库中的VirtualScroller组件,并将其注册为本组件的子组件。
然后,我们使用ref函数定义了一些响应式变量,包括:
items:需要渲染的数据列表;itemHeight:列表项的高度;totalItems:数据列表的总长度;overscanCount:超出可见区域的缓冲项数;keyField:用于识别每个数据项的键名。
接着,在template中,我们使用VirtualScroller组件,并传入上述响应式变量作为参数。我们还为组件的默认插槽提供了一个模板,用于渲染每个数据项。
最后,在setup函数中,我们定义了一个onRangeChange函数,用于处理range-change事件,该事件在可见区域发生变化时触发。我们将该函数绑定到组件的range-change事件上,并在控制台中打印出当前可见区域的范围。
总结
虚拟列表是一种优化长列表渲染性能的有效技术,通过动态渲染可视区域内的元素,可以大大减少DOM操作和重新渲染的次数,从而提高性能。