首先我没遇到过这种需求,只是图一乐而已,顺便锻炼一下能力。
不过就算上百条数据用这方法也能提升性能,所以我不能不会啊。
思路
- 准备容器,设置overflow: auto
- 子容器装着固定长度的数据,防止内存溢出
- 滚动时,实时计算子容器数据,设置
scroll - 设置节流避免频繁计算
实现
先看HTML
.container {
overflow: auto;
margin: 10px;
}
ul {
background-color: lightcoral;
}
<div class="container">
</div>
就这点就够了,我们使用JS来实现
// 后端数据
const data = new Array(9999999).fill(0).map((_, i) => i);
// 配置
const bef = 10, // 前后可多查看的条数
aft = 10,
container = document.querySelector('.container'),
containerHeihgt = 500,
perHeight = 30,
totalHeight = perHeight * data.length;
// 可视数量
const num = Math.ceil(containerHeihgt / perHeight);
let st = 0,
end = num;
首先准备配置文件
function createDOM() {
const newData = data.slice(st, end + 1),
lis = newData.map((item) => `<li style="height: ${perHeight}px">${item}</li>`);
const innerHTML = `<ul style="height: ${totalHeight}px">${lis.join('')}</ul>`;
container.innerHTML = innerHTML;
}
截取可视数据,放入新数组,生成DOM
接下来是重点了,怎么让滚动时,数据跟着变化呢,首先直接绑定个事件先
container.onscroll = onScroll;
那么接下来我们要做什么呢?
- 求出滚动时产生的数据,与界面显示的关系
- 根据数据关系,算出起始/ 结束索引
- 设置
ul的位置,让他一直处于容器可视位置
ok,接下来我们来实现
function onScroll() {
const [stIndex, endIndex] = getIndex(this.scrollTop);
setData(stIndex, endIndex);
setPos();
}
我们只要实现这三个函数,即可完成
获取索引
function getIndex(top) {
end = Math.ceil((top + containerHeihgt) / perHeight) + aft;
st = Math.floor(top / perHeight) - bef;
return [st, end];
}
(超出距离 + 容器高度) / 每一项高度 = 结束索引
超出距离 / 每一项高度 = 开始索引
再把配置文件的前后可多查看的条数加上
有可能用户滑动的距离,正好超过了一内内,所以end结束索引用天花板函数,st反之用地板砖函数
来看看有没有问题
为什么索引变成负数呢?
这时因为我们配置可超出视距范围参数bef & aft
我开头设置的是10
所以当滚动触发时,如果st < 10,就会变成负数,只需要加个判断即可
st < 0 && (st = 0)
来看下一个问题
这个4是可见的,但是起始索引却是5
为什么啊??我不是向下取整了吗?
我们到开发者工具仔细看看
腚眼一看,噢,源濑市margin导致的
我们把margin设置为0,再来看看页面,是不是只能看见5了
所以说,初始化样式真的很重要,能让你少debugger很多时间
设置数据
function setData(stIndex, endIndex) {
st = stIndex;
end = endIndex;
createDOM();
}
上面有了索引,所以我们改动开头的索引,重复调用createDOM即可,这就是低耦合度的好处。
调用后只是重置了数据,但是滚动后位置还没设置呢
设置位置
function setPos() {
const ul = container.querySelector('ul');
ul.style.transform = `translateY(${(st) * perHeight}px)`;
}
只需要用CSS的transform改动一下Y轴即可
ok,大功告成
可以看到,非常丝滑,DOM数量也是固定的,不会因为节点过多影响性能
还有一个问题,触发太频繁了看到了吗,滑动时div一直闪烁,因为一直在重新替换元素
所以还是传统艺能,节流
function throttle(fn, duration = 50) {
let st = Date.now();
return function () {
const now = Date.now();
if (now - st >= duration) {
st = now;
return fn.apply(this, arguments);
}
};
}
container.onscroll = throttle(onScroll);