前端怎样按可见比例加载列表项
来源:17golang原创
时间:2026-10-18 15:18:15 251浏览 收藏
前端怎样按可见比例加载列表项
列表里有不少卡片,但没有必要一进入页面就填充所有详情。这里把目标限定为:已经存在的卡片,进入视口的面积达到自身面积的 50% 时加载一次;组件卸载后,不再向旧 DOM 写入结果。这不是滚动到底翻页,也不是曝光统计系统。
本文依据 Intersection Observer 的平台说明与规范。官方说明:https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API;规范:https://w3c.github.io/IntersectionObserver/。以下示例用于说明实现方式,未执行浏览器实测。
先弄清楚 50% 的分母
intersectionRatio 大致表示目标元素交集矩形面积与目标边界矩形面积之比,不是目标占整个屏幕的比例,也不保证用户真正看到了内容。默认观察并不负责判断遮挡、透明度等视觉因素。
threshold: 0.5 是通知阈值,不是“回调内所有条目都已经达到一半”的保证。开始观察会产生初始通知,比例向上或向下跨过阈值也可能通知。因此要同时检查 isIntersecting 和 intersectionRatio >= 0.5。

一个可直接接入现有列表的实现
先让卡片有稳定的占位尺寸。下面两条演示数据都在本地,便于把注意力放在观察和清理上;它们不代表网络懒加载节省量。真实项目可替换 loadItem,并在请求中使用传入的 signal。
-
卡片 A
等待进入视口
-
卡片 B
等待进入视口
// 返回清理函数,由组件的卸载钩子调用
function mountLazyItems(list, loadItem) {
const ratio = 0.5;
const controller = new AbortController();
const started = new WeakSet();
let alive = true;
let observer = null;
const items = list.querySelectorAll("[data-item-id]");
async function loadOnce(item) {
// 同一批通知或重复跨阈值都不能触发重复加载
if (!alive || started.has(item)) return;
started.add(item);
if (observer) observer.unobserve(item);
const body = item.querySelector("[data-body]");
body.textContent = "加载中";
try {
const text = await loadItem(item.dataset.itemId, {
signal: controller.signal
});
// 清理后即使请求成功,也不能更新旧卡片
if (!alive) return;
body.textContent = text;
} catch (error) {
if (!alive || controller.signal.aborted) return;
body.textContent = "加载失败,请刷新后重试";
}
}
if ("IntersectionObserver" in window) {
observer = new IntersectionObserver(entries => {
for (const entry of entries) {
if (entry.isIntersecting &&
entry.intersectionRatio >= ratio) {
void loadOnce(entry.target);
}
}
}, {
root: null,
rootMargin: "0px",
threshold: ratio
});
for (const item of items) observer.observe(item);
} else {
// 降级为立即加载,不能承诺仍按可见比例触发
for (const item of items) void loadOnce(item);
}
return function cleanup() {
alive = false;
if (observer) observer.disconnect();
controller.abort();
};
}
// 本地演示加载器;真实请求需要主动使用 signal
const demoData = { a: "卡片 A 的详情", b: "卡片 B 的详情" };
const cleanupCards = mountLazyItems(
document.querySelector("#cards"),
async (id, { signal }) => {
if (signal.aborted) {
throw new DOMException("已取消", "AbortError");
}
return demoData[id] ?? "没有详情";
}
);
// 页面或组件真正卸载时调用 cleanupCards(),不要立即调用
这个实现只观察挂载时已存在的节点。追加卡片时,需要在组件维护的观察器上显式注册新节点,或清理后重新挂载;不要误以为 querySelectorAll 会自动追踪后来插入的节点。若替换整个列表,也应先执行旧的清理函数。
失败策略也刻意保持明确:该次挂载中只尝试一次,显示失败提示,不因反复滚动而无限请求。若需要按钮重试,应另行维护失败状态和重试入口,同时清理按钮监听器。
rootMargin 是提前量,不是阈值的另一种写法
root: null 使用顶层视口作为根。当前示例选择 rootMargin: "0px",让比例相对于未扩展的根边界计算,但祖先裁剪仍可能影响交集。
如果改成 rootMargin: "0px 0px 300px 0px",根边界下方被扩展,用于更早预加载。此时达到 0.5 是相对于扩展后的观察区域,不能把它解释为“屏幕里已经看见卡片的一半”。我会把“按实际几何可见比例加载”和“接近视口先预取”作为两种配置,而不是混用同一个曝光口径。
在独立滚动容器内观察时,root 应明确指向对应容器,并确认目标处于根的后代关系中。负 margin 会收缩根边界,通常意味着更晚满足条件。
卸载时需要处理三件不同的事

unobserve(item) 停止观察一张已经开始加载的卡片,配合 WeakSet 去重;disconnect() 解除整个观察器的目标关联;AbortController.abort() 通知支持 signal 的异步操作取消。三者不能互相替代。
此外,alive 标记防止清理后继续写 DOM。即使自定义加载器忽略 signal,或者异步操作已经完成,也要在结果写入前再检查组件是否还有效。观察回调应保持轻量,不要把大量同步解析或布局计算塞在回调里。
预期现象与两个容易踩的边界
预期行为是:初始不足 50% 的卡片仍保留占位文字;达到条件后变成“加载中”,随后显示详情;再次移出再进入不会重复加载。这是阅读代码得到的预期,不是实测记录。初始已经满足条件的卡片可以直接开始加载,不必等待用户滚动一次。
第一,特别高的卡片可能永远无法在视口里展示一半面积。若卡片高度明显大于视口,应改用更小比例、固定尺寸的触发区域,或采用接近视口预加载方案,不能坚持所有卡片统一 0.5。
第二,占位布局如果在图片加载后大幅改变,比例也会变。为媒体预留尺寸,同时避免加载详情后尺寸来回震荡。IntersectionObserver 提供异步阈值通知,不是连续、逐像素的滚动采样器。
这类需求的关键不在监听多少次 scroll,而在先定义比例口径:用 threshold 接收跨阈值通知,用回调条件决定是否加载,用清理函数结束观察与异步结果的生命周期。
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
404 收藏
-
410 收藏
-
312 收藏
-
236 收藏
-
174 收藏
-
153 收藏
-
465 收藏
-
376 收藏
-
467 收藏
-
473 收藏
-
401 收藏
-
217 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习