前端基于 IntersectionObserver 更流畅的懒加载实现

文章目录

    • [🌟 一、什么是 IntersectionObserver?](#🌟 一、什么是 IntersectionObserver?)
    • [🧩 二、懒加载的常见应用场景](#🧩 二、懒加载的常见应用场景)
    • [⚙️ 三、功能目标](#⚙️ 三、功能目标)
    • [💻 四、核心实现代码](#💻 四、核心实现代码)
    • [🔍 五、实现思路拆解](#🔍 五、实现思路拆解)
      • [1. 设置观察目标](#1. 设置观察目标)
      • [2. 创建 IntersectionObserver 实例](#2. 创建 IntersectionObserver 实例)
      • [3. 首次加载保护](#3. 首次加载保护)
      • [4. 页码变化时加载下一页](#4. 页码变化时加载下一页)
    • [💡 六、rootMargin 的妙用](#💡 六、rootMargin 的妙用)
    • 总结

在现代前端开发中, 懒加载(Lazy Loading) 已经成为优化性能和提升用户体验的标配功能之一。尤其是在列表类页面(如文章列表、制度文档、图片墙等)中, 一次性加载所有数据不仅浪费流量,还可能造成页面卡顿

本文将以一个实际项目为例,讲解如何使用 IntersectionObserver API 实现优雅的前端懒加载。

🌟 一、什么是 IntersectionObserver?

IntersectionObserver 是浏览器原生提供的一个 可观察元素可见性变化的 API。简单来说,它能帮我们"监听"某个元素是否出现在视口中。

当元素进入或离开视口时,回调函数会被触发。

核心优势:

  • 不需要监听 scroll 事件;
  • 浏览器原生支持,性能更好;
  • 可配置 rootMargin(提前加载距离);
  • 异步执行,不阻塞主线程。

🧩 二、懒加载的常见应用场景

  • 图片懒加载:只有当图片进入视口时才加载资源;
  • 无限滚动列表:当滚动到底部时,自动加载下一页;
  • 广告曝光监测:检测广告区域是否被用户看到。

本文聚焦第二种:滚动到底部自动加载更多数据

⚙️ 三、功能目标

我们的目标是构建一个"廉政制度"页面,当用户滚动到列表底部时自动加载下一页数据。

主要功能包括:

  1. 初次加载数据
  2. 输入关键词搜索
  3. 滚动到底部触发加载更多
  4. 防止首次渲染误触发
  5. 数据加载完成后关闭加载提示

如图所示:

💻 四、核心实现代码

来看关键部分的实现逻辑(已省略部分业务代码):

ts 复制代码
import { useEffect, useState, useRef, useCallback } from "react";
import { getEthicsRegulationList } from "../../api/backendApi";

function PolicyList() {
  const [regulations, setRegulations] = useState([]);
  const [page, setPage] = useState(0);
  const [hasMore, setHasMore] = useState(true);

  const observerRef = useRef<HTMLDivElement | null>(null);
  const firstLoadRef = useRef(true); // ✅ 初次加载保护标志

  // 获取数据
  const fetchData = useCallback(
    async (reset = false) => {
      const res = await getEthicsRegulationList({ page, size: 7 });
      if (reset) {
        setRegulations(res.content || []);
      } else {
        setRegulations((prev) => [...prev, ...(res.content || [])]);
      }
      setHasMore(!res.last);
    },
    [page]
  );

  // 初始化加载
  useEffect(() => {
    fetchData(true);
  }, []);

  // IntersectionObserver 实现懒加载
  useEffect(() => {
    if (!observerRef.current) return;

    const observer = new IntersectionObserver(
      (entries) => {
        const entry = entries[0];
        // ✅ 避免第一次渲染时触发加载
        if (entry.isIntersecting && hasMore && !firstLoadRef.current) {
          setPage((prev) => prev + 1);
        }
      },
      { rootMargin: "100px" } // 提前 100px 触发加载
    );

    observer.observe(observerRef.current);
    return () => observer.disconnect();
  }, [hasMore]);

  // 页码变化时加载下一页
  useEffect(() => {
    if (page === 0) return;
    fetchData();
  }, [page]);

  // 首次加载完成后,关闭"首次加载保护"
  useEffect(() => {
    if (regulations.length > 0) {
      firstLoadRef.current = false;
    }
  }, [regulations]);

  return (
    <div className="policy-list">
      {regulations.map((item, index) => (
        <div key={index} className="policy-card">
          {item.title}
        </div>
      ))}

      {/* 观察点(加载触发点) */}
      <div ref={observerRef} className="loading-more">
        {hasMore ? "加载更多..." : "已加载全部"}
      </div>
    </div>
  );
}

export default PolicyList;

🔍 五、实现思路拆解

1. 设置观察目标

我们在列表底部放置一个空的 <div>

html 复制代码
<div ref="{observerRef}" className="loading-more"></div>

这个元素就是"触发点"------当它出现在视口中时,就意味着用户滚动到底部。

2. 创建 IntersectionObserver 实例

js 复制代码
const observer = new IntersectionObserver(
  (entries) => {
    const entry = entries[0];
    if (entry.isIntersecting && hasMore) {
      setPage((prev) => prev + 1);
    }
  },
  { rootMargin: "100px" }
);
  • entries[0].isIntersecting:表示目标元素是否进入视口;

  • rootMargin:设置"提前加载"的距离;

    • 例如 "100px" 表示当元素距离视口底部 100px 时就触发。

3. 首次加载保护

首次渲染时,观察目标往往默认可见,会误触发加载。

因此使用一个 useRef 标记:

js 复制代码
const firstLoadRef = useRef(true);

useEffect(() => {
  if (regulations.length > 0) {
    firstLoadRef.current = false;
  }
}, [regulations]);

在懒加载逻辑中增加判断:

js 复制代码
if (entry.isIntersecting && hasMore && !firstLoadRef.current) {
  setPage((prev) => prev + 1);
}

4. 页码变化时加载下一页

js 复制代码
useEffect(() => {
  if (page === 0) return; // 初次加载已处理
  fetchData();
}, [page]);

page 变化时重新请求数据,并将结果追加到已有数据中。

💡 六、rootMargin 的妙用

rootMargin 是懒加载的"提前量"。假设设置为 "100px",意味着当底部触发点距离视口底部还有 100px 时就开始加载,避免滚动到底才触发、用户等待的卡顿感。

总结

对比项 IntersectionObserver scroll 事件监听
性能 异步执行,浏览器优化 高频触发,需手动节流
可维护性 简洁明了,配置灵活 逻辑复杂
支持性 现代浏览器原生支持 所有浏览器通用

💬 懒加载并不只是"少加载",而是让"用户感觉快"。

这才是现代前端性能优化的核心思想。


👉点击进入 我的网站

相关推荐
不绝19117 分钟前
UGUI——进阶篇
前端
Exquisite.38 分钟前
企业高性能web服务器(4)
运维·服务器·前端·网络·mysql
2501_944525541 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 账户详情页面
android·java·开发语言·前端·javascript·flutter
2601_949857431 小时前
Flutter for OpenHarmony Web开发助手App实战:快捷键参考
前端·flutter
wangdaoyin20101 小时前
若依vue2前后端分离集成flowable
开发语言·前端·javascript
心柠2 小时前
vue3相关知识总结
前端·javascript·vue.js
Amumu121382 小时前
Vue Router(二)
java·前端
a1117763 小时前
图书借阅管理系统(FastAPI + Vue)
前端·vue.js·fastapi
常年游走在bug的边缘3 小时前
掌握JavaScript作用域:从函数作用域到块级作用域的演进与实践
开发语言·前端·javascript
极致♀雨3 小时前
vue2+elementUI table表格勾选行冻结/置顶
前端·javascript·vue.js·elementui