渲染
一次性渲染大量数据不卡顿
原理: 时间换空间,使用 requestIdleCallback 或者 requestAnimationFrame 调度渲染。 缺点: 大量dom需要占用较多内存
<body>
<ul id="list"></ul>
<script>
function render() {
const FRAME_TIME = 1000 / 60; // 定义每帧的时长
const TOTAL_ITEMS = 100000; // 总条数
const ITEMS_PER_RENDER = 20; // 每次渲染20条
const RENDER_COUNT = TOTAL_ITEMS / ITEMS_PER_RENDER;
const list = document.getElementById('list');
let renderedCount = 0;
function renderItem(deadline) {
if (deadline ? deadline.timeRemaining() > 0 : true) {
// 1. 使用文档碎片优化一次插入优化性能
const fragment = document.createDocumentFragment();
for (let i = 0; i < ITEMS_PER_RENDER; i++) {
const item = document.createElement('li');
item.innerText = Math.floor(Math.random() * TOTAL_ITEMS);
fragment.appendChild(item);
}
list.appendChild(fragment);
renderedCount += 1;
}
loop();
}
function loop() {
// 使用 requestIdleCallback 或者 requestAnimationFrame 函数循环渲染 li 元素,避免卡顿
if (renderedCount < RENDER_COUNT) {
// 2. 兼容性处理,使用 requestIdleCallback 或 requestAnimationFrame
if ('requestIdleCallback' in window) {
window.requestIdleCallback(renderItem);
} else {
scheduleAnimationTask(renderItem);
}
}
}
function run() {
if ('requestIdleCallback' in window) {
window.requestIdleCallback(renderItem);
} else {
scheduleAnimationTask(renderItem);
}
}
function scheduleAnimationTask(callback) {
let startTime = Date.now();
requestAnimationFrame(() => {
if (Date.now() - startTime < FRAME_TIME) {
callback();
} else {
scheduleAnimationTask(callback);
}
});
}
run();
}
render()
</script>
</body>虚拟列表实现
原理:按需渲染, 只渲染视口范围内可见的元素。 步骤:
初始化:每一项高度预估一个默认值,用一个元素如这里的scrollHold元素撑开滚动条高度。
根据滚动高度确定视口元素的起始位置start,计算视口的区间元素位置positions[start,end]
根据start计算滚动内容容器的偏移量
currentOffset利用css的transform设置滚动内容容器的偏移量渲染后计算实际内容高度记录下来,更新虚拟滚动条内容高度。
TODO:
使用骨架屏作为缓冲区。
算法上的优化缓存高度等。
滚动获取数据,请求过程中显示骨架屏。数据渲染显示数据隐藏骨架屏。
Last updated