懒加载优化方案——IntersectionObserver

在加载图片列表时候,如果不做任何处理,浏览器会创建所有img标签并去加载每一张图片,当图片过大,或者过多的时候,页面将超级消耗资源,这时候,懒加载就起到了一个很重要的作用(如果有运维支持,当这篇文章不存在),只展示用户看到的(屏幕内的),所以官网提供了一个原生的APIIntersectionObserver接口;

从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。

说白了,就是可判断监听元素与根是否有交叉

先创建一个页面,将这几个区域绘制出来,为了方便演示,结构可能比较反人类,凑合看~

div>ul>li*100>div>p{$}+img[src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b938334aec0d4f1886d0a16e45bcadc6~tplv-k3u1fbpfcp-watermark.image?"] 在vscode中输入这行指令,便可以创建出100个li并在里面加了序号和图片,

蓝色部分将作为根,粉色框为常显示,彩色背景为懒加载内容,这样比较方便观察,超出根的元素,彩色背景图消失,粉色框用来对比的,

基本用法

首先监听div的滚动,改变ul的top实现手动滚动方式

typescript 复制代码
const div = document.querySelector('div')
const ul = document.querySelector('ul')
div?.addEventListener('wheel', (e) => {
    console.log();
    if (ul) {
        const n = ul.offsetTop
        if (e.wheelDelta > 0) {
            const top = n + 6;
            ul.style.top = top + 'px'
        } else {

            const top = n - 6;
            ul.style.top = top + 'px'
        }

    }
})

然后循环li进行监听

typescript 复制代码
const lis = document.querySelectorAll('li')
for (let i = 0; i < lis.length; i++) {
    const li = lis[i]
    const liDiv = li.querySelector('div')
    let intersectionObserver = new IntersectionObserver((entries) => {
        const e = entries[0]
        if(liDiv) {
            // 根据api返回的isIntersecting字段判断某元素是否展示
            
            liDiv.style.display = e.isIntersecting ? "block" : 'none'
        }
    }, {
        root: div, // root  根
    });
    // 开始监听
    intersectionObserver.observe(li);
}

问题

官网上提供的判断方式是if (entries[0].intersectionRatio <= 0) return;,根据intersectionRatio值是否大于零表示监听元素与根是否交叉,但是会有一个问题,滚轮滑动速度匀速还可以,但是一旦滚动速度稍微慢点,返回的值还是0,这种情况属于偶现,万能的测试提的bug。。。使用时请注意,在当前demo并没复现,原谅不能抛项目的画面,大家使用时注意就好。

优化

IntersectionObserver构造函数接受两个参数,第一个为监听回调,第二个参数options(可选),包含三个参数,

  1. root根,也就是判断交叉的视口;
  2. rootMargin盒模型边界偏移量,也相当于扩展的范围,参数跟css的属性参数写法一样 '40px 40px 40px 40px',类似这种,可以实现预加载的功能;
  3. threshold,一个归一化的比例值,比如0 为'0%',0.3,为'30%',这个数值代表交叉的范围比例,如果是'50%',则根与被监听的元素交叉比例达到一半才去判定是否真正交叉

这样,我们就可以根据这些值,和其他方案结合,对页面进行优化,从上图能看出已经加载出来的元素也被反复的隐藏和显示(渲染或销毁),渲染过程反而更消耗性能,我们需要判断元素是否已经渲染过,如果已经渲染过,则下次超出边界不销毁,需要给被监听的元素添加一个属性flag用来标记,回调函数方法改造如下:

typescript 复制代码
const flag = li.getAttribute('flag')
if (flag !== 'true') {
    const cross = e.intersectionRatio > 0 || e.isIntersecting
    if(cross) {
        li.setAttribute('flag', 'true')
    }
    liDiv.style.display = cross ? "block" : 'none'
}

或许你可以自己选择一个喜欢的属性,再结合滚动加载和indexDB,就算后台返回一万条数据,也丝毫没有渲染压力,一般后台返回大数据量,都是城市信息,轨迹信息等,我还真没见过一次性请求那么多图片的接口

以上内容在官网都有解释,只不过太多专业的名词,我大概根据自己的理解转成大白话写出来,还有很多没写在这里的API,请大家自主学习一下~

案例地址

下面是使用react封装的组件,开箱即用:

react-intersection.tsx

这个组件仅满足最基本的构建监听和销毁,如果需要其他需求,可以自己开发脑洞

相关推荐
AlienZHOU3 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc6 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师7 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen8 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒8 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow9 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林8189 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang800789 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明9 小时前
第 6 章 组件进阶
前端·vue.js
江华森9 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端