图片懒加载实现方法

270 阅读4分钟

目的

1、网页优化,提高网页加载速度

2、页面优化友好,提高SEO收录与排名

3、提高用户体验,减少服务器压力

原理

一张图片就是一个<img>标签,浏览器是否发起请求图片是根据<img>的src属性,所以实现懒加载的关键就是,在图片没有进入可视区域时,先不给<img>的src赋值,这样浏览器就不会发送请求了,等到图片进入可视区域再给src赋值。

效果预览

实现方式

图片懒加载的方式主要有两种:

1、利用 getBoundingClientRect API得到当前元素与视窗的距离来判断

2、利用h5的新API IntersectionObserver 来实现

一、getBoundingClientRect API

Element.getBoundingClientRect() 方法返回值是一个 DOMRect 对象,包含了该元素一组矩形的集合:是与该元素相关的css边框集合(top, left, right, bottom)。

代码如下:

html


    <div class="container">
      <header>头部</header>
      <main id="main">
        <div class="list">
          <img
            src="./images/default1.png"
            data-src="./images/zs1.jpg"
            alt=""
            class="lazyload"
          />
        </div>
        <div class="list">
          <img
            src="./images/default1.png"
            data-src="./images/zs2.jpg"
            alt=""
            class="lazyload"
          />
        </div>
        <div class="list">
          <img
            src="./images/default1.png"
            data-src="./images/zs3.jpeg"
            alt=""
            class="lazyload"
          />
        </div>
        <div class="list">
          <img
            src="./images/default1.png"
            data-src="./images/zs4.jpeg"
            alt=""
            class="lazyload"
          />
        </div>
        <div class="list">
          <img
            src="./images/default1.png"
            data-src="./images/zs5.jpg"
            alt=""
            class="lazyload"
          />
        </div>
        <div class="list">
          <img
            src="./images/default1.png"
            data-src="./images/zs6.jpeg"
            alt=""
            class="lazyload"
          />
        </div>
        <div class="list">
          <img
            src="./images/default1.png"
            data-src="./images/zs7.jpeg"
            alt=""
            class="lazyload"
          />
        </div>
        <div class="list">
          <img
            src="./images/default1.png"
            data-src="./images/zs8.jpeg"
            alt=""
            class="lazyload"
          />
        </div>
      </main>
      <footer>底部</footer>
    </div>

css:

    <style>
      body {
        margin: 0;
        padding: 0;
      }
      .container {
        position: relative;
      }
      header,
      footer {
        position: fixed;
        width: 100%;
        height: 50px;
        text-align: center;
        line-height: 50px;
        background: #eee;
        color: #333;
        z-index: 10;
      }
      header {
        top: 0;
      }
      footer {
        bottom: 0;
        left: 0;
      }
      main {
        width: 100%;
        height: 100%;
        position: absolute;
        top: 50px;
        bottom: 50px;
        overflow-y: scroll; // 方法一此样式删除,方法二添加
      }
      .list {
        width: 50%;
        /* height: 300px; */
        margin: 0 auto 20px;
        text-align: center;
      }
      .list img {
        max-width: 100%;
        max-height: 100%;
      }
    </style>

js:

此写法来源于麻油,初次看到这篇文章,发现其写法简洁明了,立马mark下来一试,结果很不错呀,暂时没想到更简洁的写法,就记录下来以供学习。

      // 图片懒加载类
    class LazyLoad {
        constructor(el) {
          // 需使用懒加载的图片集合
          this.imglist = Array.from(document.querySelectorAll(el));
          this.init(); // 初始化
        }
        // 获取图片与窗口信息
        getBound(el) {
          let bound = el.getBoundingClientRect();
          let clientHeight = window.innerHeight;
          // 图片距离顶部的距离 <= 浏览器可视化的高度,从而推算出是否需要加载
          return bound.top <= clientHeight - 50; // -50是头部的距离
        }
        // 加载图片
        loadImage(el, index) {
          let src = el.getAttribute("data-src");
          el.src = src;
          // 避免重复判断,已经确定加载的图片应当从imglist移除
          this.imglist.splice(index, 1);
        }
        // 判断是否该图片可以加载
        canLoad() {
          let imglist = this.imglist;
          for (let i = imglist.length; i--; ) {
            this.getBound(imglist[i]) && this.loadImage(imglist[i], i);
          }
        }
        // 当浏览器滚动的时候,继续判断
        bindEvent() {
          window.addEventListener("scroll", () => {
            this.imglist.length && this.canLoad();
          });
        }
        // 初始化
        init() {
          this.canLoad();
          this.bindEvent();
        }
      }

      // 实例化对象,参数则是需要使用懒加载的图片类名
      const lazy = new LazyLoad(".lazyload");

二、IntersectionObserver

API介绍:

IntersectionObserver

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

当一个IntersectionObserver对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦IntersectionObserver被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。

作为一个新型的API,会有一定的兼容性,查看兼容性

属性

root: 用于观察的根元素,默认是浏览器的视口,也可以指定具体元素,指定元素的时候用于观察的元素必须是指定元素的子元素

const options = {
    root: document.querySelector('.box')
}

rootMargin: 用来扩大或者缩小视窗的的大小,使用css的定义方法,10px 10px 30px 20px表示top、right、bottom 和 left的值

thresholds: 用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是[0]。

const options = {
    root: null,
    threshold: [0, 0.5, 1]
}
var io = new IntersectionObserver(callback, options)
io.observe(document.querySelector('img'))

上面代码,我们指定了交叉比例为0,0.5,1,当观察元素img0%、50%、100%时候就会触发回调函数

方法

IntersectionObserver.disconnect: 停止所有监听工作

IntersectionObserver.observe: 开始监听一个目标元素

IntersectionObserver.takeRecords: 返回所有观察目标对象的数组

IntersectionObserver.unobserve: 停止监听特定目标元素。

callback函数

当元素的可见性变化时,就会触发callback函数。

callback函数会触发两次,元素进入视窗(开始可见时)和元素离开视窗(开始不可见时)都会触发。

callback有个entries参数,它是个IntersectionObserverEntry对象数组,它的属性如下:

boundingClientRect 目标元素的矩形信息
intersectionRatio 相交区域和目标元素的比例值 intersectionRect/boundingClientRect 不可见时小于等于0
intersectionRect 目标元素和视窗(根)相交的矩形信息 可以称为相交区域
isIntersecting 目标元素当前是否可见 Boolean值 可见为true
rootBounds 根元素的矩形信息,没有指定根元素就是当前视窗的矩形信息
target 观察的目标元素
time 返回一个记录从IntersectionObserver的时间到交叉被触发的时间的时间戳

懒加载代码实现:

普通写法:

      const options = {
        root: document.querySelector(".main")
      };
      const io = new IntersectionObserver(callback, options);
      let imgs = document.querySelectorAll(".lazyload");
      function callback(entries) {
        entries.forEach(item => {
          if (item.isIntersecting) {
            item.target.src = item.target.dataset.img;
            io.unobserve(item.target);
          }
        });
      }
      imgs.forEach(item => {
        io.observe(item);
      });

es6函数封装

    class LazyLoad {
        constructor(config) {
          this.imgs = []; // 观察节点集合
          this.io = null; // 实例函数
          // 默认参数
          this.default = {
            root: null
          };
          this.settings = { ...this.default, ...config };
          this.init();
        }

        // 初始化
        init() {
          this.imgs = Array.from(document.querySelectorAll("[data-src]"));
          this.getObserver(); // 实例化
          this.addObserver(); // 开始观察
        }

        // 开始观察,观察节点
        addObserver() {
          this.imgs.forEach(item => {
            this.io.observe(item);
          });
        }
        // 实例化
        getObserver() {
          // 配置,指定可视区域,root默认为浏览器视口
          let options = {
            root: this.settings.root
          };
          console.log(this.settings.root);
          this.io = new IntersectionObserver(entries => {
            entries.forEach(item => {
              // 当前元素可见
              if (item.isIntersecting) {
                this.loadImage(item.target); // 替换图片
                this.io.unobserve(item.target); // 停止观察当前元素,避免不可见时候再次调用callback函数
              }
            });
          }, options);
        }

        // 显示图片
        loadImage(el) {
          el.src = el.dataset.src;
        }
      }
      const config = {
        root: document.querySelector(".main")
      };
      // config可不传,不传root默认为浏览器视口
      const lazyload = new LazyLoad(config);

后记

代码放在已更新到github

记录学习过程,好记性不如烂笔头~