无限长滚动列表(虚拟列表)的实现
原理:监听容器滚动,根据滚动距离生成对应列表,再利用绝对定位,将列表展示到容器的对应位置。
1、预先处理容器样式与生成数据
- 容器列表的最大高度为500px,列表项(li)的高度定为50px,即容器只展示10条数据。
<style>
ul.container {
position: relative;
width:500px;
max-height: 500px; /* 限定容器展示区域的大小 */
margin: 0;
padding: 0;
border: 1px solid #000;
overflow-y: auto;
list-style-type: none;
}
ul.container li {
position: absolute;
width: 100%;
height: 50px;
color: white;
text-align: center;
line-height: 50px;
}
</style>
...
<script>
const dataList = Array.from({ length: 1000 }).map((item, index) => index); // 生成一千条数据
const $list = document.querySelector(".container");
$list.setAttribute("style", `height: ${50 * dataList.length}px;`); // 设置列表的总高度
</script>
2、创建列表项函数
- 函数功能:根据 i 值切割数据,生成对应的 li 。在首次调用时,渲染前size条数据。
function createItem(i) {
// $list 容器, size为展示数量, i为第一条数据的索引值
$list.innerHTML = dataList.slice(i, i + size).map(
item =>
`<li style="top: ${item * 50}px; background: ${createHexColor()};">${item}</li>`)
.join("");
}
- 要点:i 值(第一条数据的索引值)该如何确定?
3、监听容器滚动确定 i 值
- 列表的滚动距离(scrollTop)除以列表项(item)的大小,可以确定第一条数据的索引值i。
$list.onscroll = handleScroll;
function handleScroll(e) {
const i = Math.floor(e.target.scrollTop / 50);
createItem(i);
}
- 监听滚动函数进行节流优化
$list.onscroll = throttle(handleScroll, 20);
function throttle(fn, delay) {
let lastTime = 0;
return function (...arg) {
const nowTime = +new Date();
if (nowTime - lastTime > delay) {
fn.apply(this, arg);
lastTime = nowTime;
}
}
}
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>virtual list</title>
<style>
ul.container {
position: relative;
width: 500px;
max-height: 500px;
/* 限定容器展示区域的大小 */
margin: 0;
padding: 0;
border: 1px solid #000;
overflow-y: auto;
list-style-type: none;
}
ul.container li {
position: absolute;
width: 100%;
height: 50px;
color: white;
text-align: center;
line-height: 50px;
}
</style>
</head>
<body>
<ul class="container">
</ul>
<script>
const dataList = Array.from({ length: 1000 }).map((item, index) => index); // 生成一千条数据
createVirtualList(dataList);
function createVirtualList(dataList) {
const $list = document.querySelector(".container"); // 生成一千条数据
$list.setAttribute("style", `height: ${50 * dataList.length}px;`); // 设置列表的总高度
const size = 20; // 由于列表只展示10条,要让列表可滚动,则需要size大于10
$list.onscroll = throttle(handleScroll, 20);
createItem(0);
function handleScroll(e) {
const i = Math.floor(e.target.scrollTop / 50);
createItem(i);
}
function createItem(i) {
$list.innerHTML = dataList.slice(i, i + size).map(
item =>
`<li style="top: ${item * 50}px; background: ${createHexColor()};">${item}</li>`)
.join("");
}
function createHexColor() {
const colorStr = "6789abcdef",
len = colorStr.length;
let colorVal = "#";
for (let i = 0; i < 6; i++) {
colorVal += colorStr[Math.floor(Math.random() * len)];
}
return colorVal
}
function throttle(fn, delay) {
let lastTime = 0;
return function (...arg) {
const nowTime = +new Date();
if (nowTime - lastTime > delay) {
fn.apply(this, arg);
lastTime = nowTime;
}
}
}
}
</script>
</body>
</html>