基于IntersectionObserver设计图片懒加载与触底加载

1,147 阅读3分钟

引言

最近在浏览器mdn时发现一个很牛逼的api,该方法主要用于监听页面元素出现在视口时的回调。我们一探究竟吧。

一:初探

1. 方法介绍

  1. 创建IntersectionObserver对象,内部传递一个回调函数,当监听的dom出现或消失在根(页面)视野时都会触发一次回调。

     const observer = new IntersectionObserver(targets=>{
         //targets是一个数组,保存被监听的dom对象
     })
     
    
  2. 监听页面指定的元素,如果想要监听多个元素,继续添加observe方法就可以。

       xxx.observe(dom1)
       xxx.observe(dom2)
       xxx.observe(dom3)
    
  3. 停止监听指定dom对象

     xxx.unobserve(dom)
    
  4. 释放监听,全局停止

     xxx.disconnect()
    

2. 例子演示

html部分

<div class="target">

css部分

body{
  height: 2000px;
  padding-top: 1000px;
}
.target {
  width: 100%;
  height: 100px;
  background-color: red;
}

js部分

let oBox = document.querySelector('.target');
const observer = new IntersectionObserver(entries => { 
  //也可以使用entries[0].isIntersecting判断是否出现视野
  if(entries[0].intersectionRatio>0) {
    console.log('出现视野');
  }else {
    console.log('消失视野')
  }
})
observer.observe(oBox);

效果演示

可以看到当盒子出现在视口与消失视口时都会触发回调函数。

QQ20230225-143845-HD.gif

二:模拟图片懒加载(原生js版+react hooks版)

js版本

html

     <div class="target">
        <div class="item">
          <img data-src="./1.jpg" alt="">
        </div>
        <div class="item">
          <img data-src="./2.jpg" alt="">
        </div>
        <div class="item">
          <img data-src="./1.jpg" alt="">
        </div>
        <div class="item">
          <img data-src="./2.jpg" alt="">
        </div>
        <div class="item">
          <img data-src="./1.jpg" alt="">
        </div>
        <div class="item">
          <img data-src="./2.jpg" alt="">
        </div>
      </div>
      

css

    body{
      height: 2000px;
    }
    .item{
      height: 250px;
      margin-bottom: 20px;
      border: 1px solid red;
    }
    .item img{
      width: 400px;
      height: 250px;
    }
    
    

js

let oBox = document.querySelectorAll('img');
const observer = new IntersectionObserver(entries => { 
  entries.forEach(item=>{
    if(item.intersectionRatio>0) {
      //1.获取图片data-src的值
      const src = item.target.getAttribute('data-src');
      //2.绑定图片的src上
      item.target.setAttribute('src',src);
      //3.停止监听当前图片对象
      observer.unobserve(item.target);//停止监听
    }
  })
})
// 添加对某个元素的监听
for(let i=0;i<oBox.length;i++) observer.observe(oBox[i]);

效果

QQ20230225-150056-HD.gif

观察左下角的dom节点变化,当页面滚动到当前图片视口时,图片自动添加src属性。

react版本

ImgLazy组件

import React,{useEffect,useRef} from "react";
const ImgLazy = (props:{src:string,alt?:string}) => {
  const {src,alt} = props;
  const imgRef = useRef<HTMLImageElement>(null);

  useEffect(()=>{
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if(entry.isIntersecting) {
          if(imgRef.current) imgRef.current.src = src;
          observer.disconnect()
        }
      })
    })
    // 添加对某个元素的监听
    imgRef.current && observer.observe(imgRef?.current);
    return ()=>{
      observer.disconnect()
    }
  },[])
  return < img alt={alt} ref={imgRef}/>
}
export default ImgLazy;

测试使用

import React from 'react';
import LazyLoad from "./useLazy";
import Img1 from "./imgs/1.jpg";
import Img2 from "./imgs/3.png";
import './App.css';
function App() {
  const imgList = [
    {src:Img1,alt:'img1'},
    {src:Img2,alt:'img2'},
    {src:Img1,alt:'img3'},
    {src:Img2,alt:'img4'},
    {src:Img1,alt:'img5'},
    {src:Img2,alt:'img6'},
  ]
  return (
    <div className='box'>
      {
        imgList && imgList.map(item=>(
          <div key={Date.now()} className='item'>
            <LazyLoad alt={item.alt} src={item.src}/>
          </div>
        ))
      }
    </div>
  )
}
export default App;

App.css

.item{
  height: 300px;
  margin-bottom: 20px;
  border: 1px solid red;
}
.item img{
  width: 500px;
  height: 250px;
}

页面效果

三:模拟触底加载

js版本

设计思路:

触底加载的思路十分明确,当浏览器滚动到列表底部时,发起新的数据请求完成数据的拼接。 那么我们基于IntersectionObserver如何实现?我们只需在列表的底部添加一个兜底盒子,当兜底盒子出现在页面视口时就触发数据请求,完成数据拼接。

html部分

    <div class="target">
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="bottom"></div>
    </div>

css部分

body{
  height: 2000px;
}
.target {
  width: 100%;
}
.item{
  width: 100%;
  height: 250px;
  border: 1px solid red;
  margin-bottom: 20px;
}
.bottom{
  width: 100%;
  height: 10px;
  background: gold;
}
 

js部分

    //1.获取兜底盒子,添加监听
    let oTarget = document.querySelector('.bottom')
    let oBox = document.querySelector('.target');
    const observer = new IntersectionObserver(entries => {
      console.log(entries)
      if(entries[0].intersectionRatio>0) {
        //2.兜底盒子出现视野范围内,继续加载数据,节点动态插入到兜底盒子前面
        for(let i=0;i<5;i++) {
          let oItem = document.createElement('div');
          oItem.className = 'item';
          oBox.insertBefore(oItem,oTarget)
        } 
      }
    })

    observer.observe(oTarget);

效果

总结

看董这两个案例,不禁感叹IntersectionObserver真踏马好用啊,谢谢各位爷支持。