当前位置:首页 > 文章列表 > 文章 > 前端 > 前端怎样按可见比例加载列表项

前端怎样按可见比例加载列表项

来源:17golang原创 2026-10-18 15:18:15 0浏览 收藏

前端怎样按可见比例加载列表项

列表里有不少卡片,但没有必要一进入页面就填充所有详情。这里把目标限定为:已经存在的卡片,进入视口的面积达到自身面积的 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 接收跨阈值通知,用回调条件决定是否加载,用清理函数结束观察与异步结果的生命周期。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
前端怎样用 AbortSignal 统一移除事件监听前端怎样用 AbortSignal 统一移除事件监听
上一篇
前端怎样用 AbortSignal 统一移除事件监听
前端 PerformanceNavigationTiming 怎样计算首字节时间
下一篇
前端 PerformanceNavigationTiming 怎样计算首字节时间
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    525次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    582次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    609次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    558次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    379次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码