React 用 IntersectionObserver 实现图片懒加载与无限滚动:封装一个 useInView Hook
React 用 IntersectionObserver 实现图片懒加载与无限滚动:封装一个 useInView Hook列表页一次渲染几十张图,首屏还没露脸的图片全都在偷偷加载,首屏白屏时间被拖长;或者做「滚到底自动加载下一页」,你监听scroll事件算scrollTop clientHeight scrollHeight,结果滚动时卡顿、还得手动防抖。这两个需求其实是同一个问题:怎么知道某个元素进没进视口。老办法(监听 scroll getBoundingClientRect)又费性能又难写,现代做法是IntersectionObserver。这篇带你封装一个可复用的useInViewHook,再分别拿它做图片懒加载和无限滚动。为什么别再用 scroll getBoundingClientRect先看看「朴素写法」为什么不好:// ❌ 老办法:监听 scroll,每次都算位置 useEffect(() { const onScroll () { const rect ref.current.getBoundingClientRect(); if (rect.top window.innerHeight) { // 进视口了... } }; window.addEventListener(scroll, onScroll); return () window.removeEventListener(scroll, onScroll); }, []);两个问题:scroll 事件触发极其频繁,滚一下能触发几十上百次,每次都调getBoundingClientRect——而getBoundingClientRect会强制浏览器同步重排(reflow),滚动时掉帧就是这么来的。你还得自己加节流,越写越复杂。多个元素都要监听时,每个都挂一个 scroll listener,性能雪上加霜。IntersectionObserver是浏览器原生 API,专门解决「元素是否进入视口」。它异步、在浏览器空闲时批量回调,不占主线程,也不触发重排,几十个元素一起观察也很轻。封装 useInView Hook核心思路:传一个 ref 给要观察的元素,Hook 内部用IntersectionObserver盯着它,返回一个inView布尔值。import { useEffect, useRef, useState } from react; function useInView(options) { const ref useRef(null); const [inView, setInView] useState(false); useEffect(() { const el ref.current; if (!el) return; // 元素还没挂载,直接跳过 const observer new IntersectionObserver(([entry]) { // entry.isIntersecting 就是「有没有和视口相交」 setInView(entry.isIntersecting); }, options); observer.observe(el); // 关键:卸载时断开观察,否则观察器和 DOM 引用泄漏 return () observer.disconnect(); }, [options]); return [ref, inView]; }几个要点:回调里的entry.isIntersecting是布尔值,true 元素和视口(或指定容器)有交集。return () observer.disconnect()不能省。组件卸载后不断开,观察器会一直持有 DOM 引用,既泄漏内存又可能对已卸载节点调 setState 报警告。依赖数组带options:如果 options 是父组件每次渲染新建的对象,会导致 effect 反复重建。用法上要么把 options 定义在组件外,要么用useMemo包住(下面会提)。options支持三个常用字段:root:相对哪个容器算「视口」,默认null即浏览器视口。做容器内滚动(不是整页滚)时传容器 ref。rootMargin:给视口「扩边」,如200px表示元素距视口还有 200px 时就算「进入」——懒加载提前触发、避免用户看到图还在转圈,全靠它。threshold:相交比例阈值,0表示露头就触发,1表示完全可见才触发。场景一:图片懒加载思路:图片没进视口时只渲染占位,inView变 true 后才把真实src挂上去。function LazyImage({ src, alt }) { // rootMargin 200px:提前 200px 就开始加载,滚到时图已就位 const [ref, inView] useInView({ rootMargin: 200px, threshold: 0 }); const [loaded, setLoaded] useState(false); return ( div ref{ref} style{{ minHeight: 200, background: loaded ? none : #f0f0f0 }} {inView ( img src{src} alt{alt} onLoad{() setLoaded(true)} style{{ opacity: loaded ? 1 : 0, transition: opacity .3s }} / )} /div ); }inView为 false 时渲染一个灰色占位(撑住高度,避免布局跳动),进视口后才渲染img真正发起图片请求;onLoad后淡入。rootMargin: 200px让加载提前发生,用户几乎感觉不到懒加载的存在。补充:现代浏览器其实原生支持img loadinglazy,简单场景直接用它就够了。但当你需要**自定义占位、控制加载时机、或懒加载的不只是图片(比如整个重型组件)**时,IntersectionObserver才是通用解。场景二:无限滚动思路:在列表末尾放一个「哨兵」空元素,它一进视口就说明用户快滚到底了,触发加载下一页。function InfiniteList() { const [items, setItems] useState([]); const [page, setPage] useState(1); const [loading, setLoading] useState(false); const [hasMore, setHasMore] useState(true); // 哨兵进视口就触发 const [sentinelRef, inView] useInView({ rootMargin: 100px }); useEffect(() { // 只有哨兵可见、不在加载中、还有更多,才加载 if (!inView || loading || !hasMore) return; setLoading(true); fetch(/api/items?page${page}) .then((r) r.json()) .then((data) { setItems((prev) [...prev, ...data.list]); // 追加,不是替换 setHasMore(data.hasMore); setPage((p) p 1); }) .finally(() setLoading(false)); }, [inView, loading, hasMore, page]); return ( div {items.map((it) ( div key{it.id}{it.title}/div ))} {hasMore div ref{sentinelRef} style{{ height: 1 }} /} {loading p加载中…/p} {!hasMore p没有更多了/p} /div ); }三个防坑点:if (!inView || loading || !hasMore) return三重闸门缺一不可。少了loading判断,哨兵持续可见时会连发好几次请求(第一页数据还没回来,哨兵还在视口里,effect 又跑一遍);少了hasMore,数据加载完后哨兵还在,会无限请求空数据。哨兵只在hasMore时渲染。数据到底后哨兵消失,observe的目标没了,自然不再触发,同时页面显示「没有更多了」。setItems((prev) [...prev, ...data.list])用函数式更新追加,别写成依赖闭包里的items,否则容易丢数据(连续加载时拿到旧的 items 快照)。一个容易踩的坑:ref 变化不会重新 observe上面的useInView有个隐含前提:被观察的 DOM 节点在组件生命周期里是稳定的。如果你的元素是条件渲染、ref 指向的节点会中途更换,useEffect的空依赖不会重新执行observe,新节点就没被观察。稳妥做法是用回调 ref 或把节点作为依赖。这里给一个更健壮的版本骨架:function useInView(options) { const [node, setNode] useState(null); // 用 state 存节点 const [inView, setInView] useState(false); const ref useCallback((el) setNode(el), []); // 回调 ref useEffect(() { if (!node) return; const observer new IntersectionObserver( ([entry]) setInView(entry.isIntersecting), options ); observer.observe(node); return () observer.disconnect(); }, [node, options]); // 节点变了就重新 observe return [ref, inView]; }用回调 ref 把节点存进 state,节点一变 effect 就重跑、重新 observe,条件渲染的元素也能正确观察。记得此时options最好用useMemo固定,否则每次渲染都重建 observer。小结判断「元素是否进视口」用IntersectionObserver,别再监听 scroll getBoundingClientRect——后者强制同步重排,滚动必卡。封装useInView的骨架:effect 里new IntersectionObserver→observe(el)→return () observer.disconnect()(卸载断开,防泄漏)。rootMargin给视口扩边,让懒加载/加载下一页提前触发;threshold控制露多少才算进入。无限滚动用末尾哨兵元素inView,并加loading/hasMore三重闸门防重复请求,追加数据用函数式更新。一句话记忆点:懒加载和无限滚动是同一个问题——「元素进视口没有」,用 IntersectionObserver 一把 Hook 全解决,记得卸载时 disconnect。

相关新闻

OpenAI API实战:AI辅助数学研究与代码生成全指南

OpenAI API实战:AI辅助数学研究与代码生成全指南

在探索人工智能与数学的交叉领域时,我们常常惊叹于大语言模型在代码生成、文本创作乃至逻辑推理上的强大能力。然而,当“OpenAI Astra”与“证明非sofic群存在”这样的数学命题联系在一起时,许多开发者可能会感到困惑:这究竟是AI在…

2026/8/4 10:30:59 阅读更多 →
《刺客信条:黑旗》100%同步与白金全收集终极攻略

《刺客信条:黑旗》100%同步与白金全收集终极攻略

1. 先明确“记忆重置”与白金攻略的核心价值 如果你正在玩《刺客信条:黑旗》,并且被“记忆重置”这个同步条件、全收集品或者各种奖杯/成就卡住,那这篇攻略就是为你准备的。它不是一个简单的剧情流程记录,而是专门针对那些需要100…

2026/8/4 10:30:59 阅读更多 →
Chapter IV — Phase-Space Projection and Steady-State Paradigms (Expanded)

Chapter IV — Phase-Space Projection and Steady-State Paradigms (Expanded)

4.2 Elliptical Orbits — Anisotropic Stability Domains “An ellipse is not a defective circle; it is the system’s honest visualization of its own fragility. Closure signifies continued viability; asymmetry flags the soft flanks that demand vigilant guardi…

2026/8/4 10:29:58 阅读更多 →

最新新闻

UE5指令化开发实战:10大高效命令提升游戏制作效率

UE5指令化开发实战:10大高效命令提升游戏制作效率

1. 项目概述:UE5指令化开发的效率革命 最近在独立游戏开发圈子里,一个话题讨论得特别热:如何用更少的代码、更直观的方式,快速实现那些看起来酷炫的次世代游戏效果?如果你还在为复杂的蓝图连线、繁琐的C编译而头疼&…

2026/8/4 11:24:20 阅读更多 →
WATERFLY运动包设计创新与轻量化技术解析

WATERFLY运动包设计创新与轻量化技术解析

1. 品牌定位与市场洞察WATERFLY作为全球领先的小型运动包品牌,其成功绝非偶然。这个创立于2015年的品牌,精准抓住了都市运动人群对轻量化装备的核心需求。在传统运动包市场被大型双肩包主导的背景下,WATERFLY敏锐地发现:现代运动爱…

2026/8/4 11:24:20 阅读更多 →
从实验到产线——AI 工作流的规模化挑战与协作生态 | 2026 ChinaJoy AI未来生态大会

从实验到产线——AI 工作流的规模化挑战与协作生态 | 2026 ChinaJoy AI未来生态大会

内容生产正在经历从“技术验证”到“产业落地”的关键跨越。 AI工作流如何从实验室的“惊艳一刻”走向产线的“日常运转”?当生成能力不再是门槛,规模化落地的瓶颈在哪里,人又该扮演什么角色? 内容生产正在经历从“技术验证”到…

2026/8/4 11:24:20 阅读更多 →
Unity - 图集大小与内存占用

Unity - 图集大小与内存占用

PC纹理格式,关闭 Mipmap(开启则约增加 1/3 内存),内存占用如下:图集尺寸(像素)RGBA32 (无损)DXT5 (有损RGBA)DXT1 (有损RGB)BC7 (高质量)512 5121 MB256 KB128 KB256 KB1024 10244 MB1 MB0.5 …

2026/8/4 11:24:20 阅读更多 →
YimMenu终极指南:3大核心理念打造最安全的GTA5游戏体验

YimMenu终极指南:3大核心理念打造最安全的GTA5游戏体验

YimMenu终极指南:3大核心理念打造最安全的GTA5游戏体验 【免费下载链接】YimMenu YimMenu, a GTA V menu protecting against a wide ranges of the public crashes and improving the overall experience. 项目地址: https://gitcode.com/GitHub_Trending/yi/Yim…

2026/8/4 11:24:20 阅读更多 →
Python+Flask构建大学生智能就业推荐系统实践

Python+Flask构建大学生智能就业推荐系统实践

1. 项目概述:基于PythonFlask的大学生就业推荐系统这个项目是一个面向大学生群体的智能求职招聘平台,采用PythonFlask作为后端核心框架,结合Vue.js前端技术栈实现前后端分离架构。系统主要解决传统招聘平台对大学生群体适配度不足的问题&…

2026/8/4 11:23:19 阅读更多 →

日新闻

AI Agent白手起家26: 使用标准事件驱动大模型实践

AI Agent白手起家26: 使用标准事件驱动大模型实践

纲要 练习目标:掌握大模型标准事件的调用回顾 LangChain 中的核心标准事件 invokestreambatchastream_eventswith_structured_output 环境准备实战代码:多种事件调用对比 同步调用与流式输出批量处理异步事件流监听结构化输出 运行说明与预期结果总结与扩…

2026/8/4 0:00:40 阅读更多 →
dealsea是什么?跨境卖家必知的美国deal站入门指南

dealsea是什么?跨境卖家必知的美国deal站入门指南

说实话,第一次听说美国这个老牌折扣网站的跨境卖家,十个有八个会问同一个问题:这个平台到底是干嘛的?我见过一个做家居出口的朋友,他在亚马逊上月销二十万美金,却从来没用过它。我给他看了首页——一屏一屏…

2026/8/4 0:01:40 阅读更多 →
清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

通讯作者:邓兵、刘建国通讯单位:清华大学DOI:https://doi.org/10.1021/acs.est.6c00603研究背景稀土元素(REEs)是清洁能源技术与电子器件不可或缺的核心原料,然而传统提取方式依赖能耗高、排放大的采矿与强…

2026/8/4 0:01:40 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/3 4:58:13 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/3 1:53:31 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/4 5:26:40 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/3 13:07:03 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/4 11:09:16 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/3 8:27:36 阅读更多 →