图片懒加载竟然如此简单?

324 阅读2分钟

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

大家好,我是大帅子,今天给大家讲一下图片的懒加载,众所周知,现在单页面的应用越来越多了,但是首屏加载的速度一直都是一个难题,那么我们该如何解决呢,今天我们就来看一下其中的一种解决方案图片懒加载吧,下面我们直接开始吧,


IntersectionObserver

目标

封装一个拥有懒加载功能的图片组件,实现对文章列表项上的封面图片进行懒加载

懒加载的基本思路

当dom元素进入可视区域时,才去加载它。

如何判断一个dom元素是否进入了可见区域?

实现思路

利用浏览器提供的 IntersectionObserver,监听图片元素是否进入可视区域,进入后才真正去设置图片元素的 src 属性进行图片加载。

格式

var dom = dom元素
​
// 1. 实例化一个观察者// 它的参数1是一个回调:当被观察的目标进入视口/离开视口就会调用
var observer = new IntersectionObserver((entries)=>{
  console.log(entries[0].isIntersecting)
  console.log(entries[0].intersectionRatio)
  if(entries[0].isIntersecting) {
    
  }
}, 其他配置)
​
// 2. 观察者观察dom
observer.observe(dom)
​
observer.disconnect()   // 停止观察者
observer.unobserve(dom) // 观察者停止对dom的观察

示例代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title></head>
<body>
    <div>
        <p style="padding: 30px;">1</p>
        <p style="padding: 30px;">2</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">4</p>
        <p style="padding: 30px;">5</p>
        <p style="padding: 30px;">6</p>
        <p style="padding: 30px;">7</p>
        <p style="padding: 30px;">8</p>
        <p style="padding: 30px;">9</p>
        <!-- <img height="200" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Ffzn.cc%2Fwp-content%2Fuploads%2F2020%2F04%2F640-8.jpg&refer=http%3A%2F%2Ffzn.cc&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1641365183&t=b5d7bdae0fe3f2c4831b52e3985abdf1" /> -->
        <img height="200" data-src="https://gimg2.baidu.com/image_search1/src=http%3A%2F%2Ffzn.cc%2Fwp-content%2Fuploads%2F2020%2F04%2F640-8.jpg&refer=http%3A%2F%2Ffzn.cc&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1641365183&t=b5d7bdae0fe3f2c4831b52e3985abdf1" />
        <p style="padding: 30px;">1</p>
        <p style="padding: 30px;">2</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">4</p>
        <p style="padding: 30px;">5</p>
        <p style="padding: 30px;">6</p>
        <p style="padding: 30px;">7</p>
        <p style="padding: 30px;">8</p>
        <p style="padding: 30px;">9</p>
    </div>
​
    <script>
        var img = document.querySelector("img")
        var observer = new IntersectionObserver((arr)=>{
       // isIntersecting 判断图片是不是在可视的区域
            console.log(arr[0].isIntersecting)
       // isIntersecting 判断图片是不是在可视的区域
            console.log(arr[0].intersectionRatio)
            if(arr[0].isIntersecting) {
                img.src = img.getAttribute('data-src')
                img.onerror = function(){
                    img.src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fhbimg.b0.upaiyun.com%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"
                }
                observer.unobserve(img)
            }
        }, {rootMargin: "100px"})
​
        observer.observe(img)
    </script>
</body>
</html>

上面就是图片懒加载的元祖,我们在平时如果想写一个这个dome的话,这个就是一个不错的选择


图片懒加载组件

import classnames from 'classnames'
import { useEffect, useRef, useState } from 'react'
import Icon from '../Icon'
import styles from './index.module.scss'



type Props = {
  src: string
  className?: string
}

const Image = ({ src, className }: Props) => {

  // 对图片元素的引用
  const [isLoading, setIsLoading] = useState(false)
  const [isError, setIsError] = useState(false)
  const imgRef = useRef<HTMLImageElement>(null)


  useEffect(() => {
    // 1. 创建 IntersectionObserver对象
    const ob = new IntersectionObserver((entry) => {
      console.log(entry[0].isIntersecting)
      if (entry[0].isIntersecting) {
        // 图片可见
        if (imgRef.current) {
          imgRef.current.src = src
          setIsLoading(true) // 浏览器去请求图片了
        }
        ob.unobserve(imgRef.current!)
      }
    })
    
    // 2. 观察img
    ob.observe(imgRef.current!)
  }, [])

  return (
    <div className={classnames(styles.root, className)}>

      {/* 情况1:正在加载时显示的内容 */}
      {
        isLoading && <div className="image-icon">
          <Icon type="iconphoto" />
        </div>
      }

      {/* 情况2:加载出错时显示的内容 */}
      {
        isError && <div className="image-icon">
          <Icon type="iconphoto-fail" />
        </div>
      }

      {/* 情况3:加载成功时显示的内容 */}
      {<img
        onError={() => { setIsError(true) }}
        onLoad={() => { setIsLoading(false); console.log('图片加载完成') }} alt="" data-src={src} ref={imgRef} />}
    </div>
  )
}

export default Image

样式

.root {
    position: relative;
    display: inline-block;
    width: 100%;
    height: 100%;

    :global {
      img {
        display: block;
        width: 100%;
        height: 100%;
      }

      .image-icon {
        position: absolute;
        left: 0;
        top: 0;
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100%;
        height: 100%;
        background-color: #f7f8fa;
      }

      .icon {
        color: #dcdee0;
        font-size: 32px;
      }
    }
  }