备战面试!十分钟学会用原生封装图片懒加载组件

234 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情

提到项目优化一定会有懒加载,尤其是在spa单页面项目中;今天我们用原生的API封装一个图片懒加载组件,很多js的工具库里面的懒加载底层原理都是使用这个IntersectionObserver API,它是浏览器提供的。所以今天我们学会封装图片懒加载组件,日后封装其他懒加载组件都是差不多的!

先看IntersectionObserver的使用方法

var dom = dom元素 //获取需要被监听的dom元素
// 实例化一个观察者
// 它的参数1是一个回调:当被观察的目标进入视口/离开视口就会调用
var observer = new IntersectionObserver((entries)=>{
console.log(entries[0].isIntersecting) //没出现在页面就是false,出现就是true
console.log(entries[0].intersectionRatio)//没出现在页面是0
if(entries[0].isIntersecting) {
}
}, {rootMargin: "100px"}) //这里的意思图片距离页面显示还有100px的时候,就开始加载,
等于一个提前亮操作
// 观察者观察dom
observer.observe(dom) 
observer.disconnect()   // 停止观察者
observer.unobserve(dom) // 观察者停止对dom的观察

observer.observe(dom) 开启观察者并观察dom

observer.disconnect() // 停止观察者

这两个开启和停止观察者的操作要写在observer实例对象的外面,写在里面实例对象不能自身调自身

我们现在html上写一个例子,先看看我们的布局

<body>
<p style="height:80px">这是一段文字</p>
<p style="height:80px">这是一段文字</p>
<p style="height:80px">这是一段文字</p>
<img height="200" data-src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1114%2F050421112027%2F210504112027-2-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652020451&t=ba76aca2e9107ae57e95116314b8309f" /> 
<!-- https://image.baidu.com/search/detail?ct=503316480&z=0&tn=baiduimagedetail&ipn=d&word=%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%A4%B1%E8%B4%A5%E7%9A%84%E5%9B%BE%E7%89%87&step_word=&ie=utf-8&in=&cl=2&lm=-1&st=-1&hd=&latest=&copyright=&cs=2129842042,3019439823&os=4208003612,901355711&simid=4111145578,827000330&pn=7&rn=1&di=7060663421280190465&ln=1555&fr=&fmq=1649428493065_R&ic=&s=undefined&se=&sme=&tab=0&width=&height=&face=undefined&is=0,0&istype=2&ist=&jit=&bdtype=0&spn=0&pi=0&gsm=0&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%253A%252F%252Fpic.soutu123.cn%252Felement_origin_min_pic%252F01%252F37%252F85%252F80573c6529bb88f.jpg%2521%252Ffw%252F700%252Fquality%252F90%252Funsharp%252Ftrue%252Fcompress%252Ftrue%26refer%3Dhttp%253A%252F%252Fpic.soutu123.cn%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Dauto%3Fsec%3D1652020491%26t%3De435ed17ff4f901c5a8ac711a36b513d&rpstart=0&rpnum=0&adpicid=0&nojc=undefined&dyTabStr=MCwzLDYsMiw0LDEsNSw3LDgsOQ%3D%3D -->
<p style="height:80px">这是一段文字</p>
<p style="height:80px">这是一段文字</p>
<p style="height:80px">这是一段文字</p>
<p style="height:80px">这是一段文字</p>
<p style="height:80px">这是一段文字</p>
<p style="height:80px">这是一段文字</p>

<script>

</script>
</body>

image.png

给p标签高度撑开页面,出现滚动条效果,并把图片挤到页面显示下,这样我们可以实现下滑出现图片

这里给src改成自定义属性,是因为默认src属性的话,会默认发请求,为了实现我们懒加载的效果,我们需要当图片出现在页面在通过dom赋值给src再发请求

<img height="200" data-src="https://gimg2.baidu.com/image_search/

接下来通过IntersectionObserver这个API实现懒加载

<script>
 let img = document.querySelector('img')
 //  第一个参数函数的形参里要传一个数组
 let ob = new IntersectionObserver((arr)=>{
    if(arr[0].isIntersecting){//等于true时,说明已经观察到图片离展示页面100px
       img.src=img.getAttribute('data-src') //在把自定义属性里面存的图片地址赋值给图片src属性
    } 
    // 给图片注册点击事件,当图片加载失败时显示失败图片
    img.onerror=function(){
             img.src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fhbimg.b0.upaiyun.co m%2Fcdb58e6860bcf06028c4b40e47aa17fd7ffa2e6f67cc-UQZRFK_fw658&refer=http%3A%2F%2Fhbimg.b0.upaiyun.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1641366439&t=07c8d22d1c35def62dcaaf04d94e1255"
    }
 },{ rootMargin: '100px' })
 ob.observe(img) //开启观察者观察dom
</script>

打开浏览器的Network,我们发现一开始进入页面是,并没有发送请求获取图片

image.png

此时我们往下滑动滚轮,当图片离展示页面100px的时候,就已经发送请求获取图片了

image.png

这时候图片已经加载完毕,继续往下滑动滚轮就可以看到图片了!

image.png

这里我还给图片注册了onerror事件,当图片加载失败时就会显示这张图片,同样也是懒加载效果,还可以定义图片再加载的时候显示等待效果图片,我们下个例子做

这里我们把图片的路径改错,就会触发onerror事件,展示错误的图片

image.png

这里先发请求找到错误图片,是因为我代码走过一次,浏览器中有缓存, 所以直接从缓存中读取的,要更快一些

然后重点我们封装一个图片懒加载组件,直接上代码

import classnames from 'classnames'
import { useEffect, useRef, useState } from 'react'
import Icon from '../Icon'
import styles from './index.module.scss'
/**
* 拥有懒加载特性的图片组件
* @param {String} props.src 图片地址
* @param {String} props.className 样式类
*/
type Props = {
src: string // 必传
className?: string // 可选
}
const Image = ({ src, className }: Props) => {
// 对图片元素的引用
const imgRef = useRef<HTMLImageElement>(null)
const [isError, setIsError] = useState(false)
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
const ob = new IntersectionObserver((entry) => {
  // 图片到了可视区域
  if (entry[0].isIntersecting) {
    //    给图片赋值地址
    imgRef.current!.src = src
    
    // 图片正在加载时显示加载图标
    setIsLoading(true)
    
    // 观察者停止对dom的观察
    ob.unobserve(imgRef.current!)
    
  }
  // 在组建销毁时,停止观察者
  return () => {
    ob.disconnect()
  }
  
  //   提前亮,等于距离还有300px的时候就提前获取图片
}, { rootMargin: '300px' }) //
// 创建实例对象后,观察者观察dom
ob.observe(imgRef.current!)
}, [])

return (
<div className={classnames(styles.root, className)}>
  {/* 正在加载时显示的内容 */}
  {
    // isLoading=true加载图标显示
    isLoading && <div className="image-icon">
      <Icon type="iconphoto" />
    </div>
  }
  {/* 加载出错时显示的内容 */}
  {
      // isLoading=true报错图标显示显示
   isError && <div className="image-icon">
      <Icon type="iconphoto-fail" />
    </div>
  }
  {/* 加载成功时显示的内容 */}
  {/* onLoad图片加载成功后的回调 */}
  {<img alt="" onError={() => setIsError(true)} onLoad={() => setIsLoading(false)} data-src={src} ref={imgRef} />}
</div>
)
}
export default Image

代码的逻辑并不难,和我们上一个例子基本一样,我们加上了等待图片加载时显示的图片,和加载错误显示的图片,这样用户的提样会更好!

然后需要懒加载的的图片直接导入组件,传递地址过去就行了,对于移动端下拉加载或者长列表中的图片优化很方便

import Image from '@/components/Image/index'
{item.cover.images.map((image, index) => (
          <div key={index} className="article-img-wrapper">
            <Image src={image}></Image>
            {/* <img src={image} alt="" /> */}
          </div>
 ))}

懒加载一个最需要注意的地方,展示的页面一定要有固定高度!!!,不然页面会被内容撑高,这个IntersectionObserver API就会失效,一定要注意!!!