Published on

关于各种图片懒加载

字多不看版:安装插件最省事并且适配的情况也多 🤣

原理

1、存储图片的真实路径,把图片的真实路径绑定给一个以 data 开头的自定义属性 data-src 即可,页面中的 img 元素,如果没有 src 属性,浏览器就不会发出请求去下载图片(没有请求就提高了性能)

<div class="scrollLoading" data-src="loaded.html">加载中...</div>

2、设置 img 的默认 src 为其他图片(所有的 img 都用这一张,只会发送一次请求)

<img  data-src="xxx" src="1px.gif" width="180" height="180"/>

3、需要一个滚动事件,判断元素是否在浏览器窗口,一旦进入视口才进行加载,当滚动加载的时候,就把这张图片替换为真正的 url 地址(也就是 data-src 里保存的值)

4、等到图片进入视口后,利用 js 提取 data-src 的真实图片地址赋值给 src 属性,就会去发送请求加载图片,真正实现了按需加载

实现图片懒加载的多种方法的区别就在于判断元素是否进入视口的方式不同,当使用方法四和方法五时,如果发生滚动事件,会产生大量的循环和判断操作判断图片是否可视区里,因此需要用节流进行优化。

方法一 lazyload 插件

vue2

github 地址:GitHub - hilongjw/vue-lazyload: A Vue.js plugin for lazyload your Image or Component in your applica

原理:Vue-lazyload 原理详解之源码解析lazyload 实现原理小七的玩偶的博客-CSDN 博客

vue-lazyload 的核心原理是利用了IntersectionObserver API,这是一个用于检测元素是否与视口相交的 API,它可以高效地监听元素的可见性变化,并触发回调函数。

vue-lazyload 在注册插件时,会创建一个全局的IntersectionObserver实例,并设置一些选项,如阈值(threshold)、根元素(root)等。然后,在绑定 v-lazy 指令时,会创建一个监听器(listener)对象,并将其添加到一个监听器队列(listenerQueue)中。每个监听器对象都包含了元素的相关信息,如状态(state)、图片地址(src)等。

接下来,vue-lazyload 会遍历监听器队列,并调用IntersectionObserver实例的observe方法,将每个元素注册到观察者中。当元素与视口相交时,IntersectionObserver实例会触发回调函数,并传入一个 entries 参数,表示所有被观察的元素的状态信息。vue-lazyload 会根据 entries 中的isIntersecting属性判断元素是否可见,如果是,则调用监听器对象的load方法,将元素的 src 或者 style 属性替换为真实的图片地址,并将该监听器对象从队列中移除。

安装

npm i vue-lazyload@1.2.3 -S

main.ts

// 1.图片懒加载插件
import VueLazyload from 'vue-lazyload'

// 2.注册插件
Vue.use(VueLazyload, {
  //参数配置 可不填

  // 懒加载默认加载图片
  loading: 'xxx.png',
  // 加载失败后加载的图片
  error: 'xxx.png',
  preLoad: 1.3, // 预加载高度的比例
  attempt: 3 // 尝试加载次数
})

可选参数配置项

vue-lazyload 提供了一个自定义指令 v-lazy,可以在 img 标签或者任何需要设置背景图片的标签上使用它。例如:

<!-- 懒加载img标签 -->
<img v-lazy="imgUrl" />

<!-- 懒加载背景图片 -->
<div v-lazy:background-image="bgUrl"></div>

v-lazy 指令接收一个字符串类型的值,表示图片的地址。如果是背景图片,需要在指令后加上:background-image修饰符。

当页面滚动时,vue-lazyload 会检测元素是否进入可视区域,如果是,则替换元素的 src 或者 style 属性,从而实现懒加载。

注意:若图片为循环渲染、分页显示,则必须写 key 值,不然切换页面后页面视图不刷新

vue3

github 地址:GitHub - caozhong1996/vue-lazyload-next: vue-lazyload for vue3

原理与使用方法与 vue2 版本插件相同,仅安装不同

安装

npm i vue-lazyload-next -S

main.ts

import VueLazyloadNext from 'vue-lazyload-next';

app.use(VueLazyloadNext, {
  // 添加一些配置参数 可不填

  // 懒加载默认加载图片
  loading: 'xxx.png',
  // 加载失败后加载的图片
  error: 'xxx.png',
  preLoad: 1.3, // 预加载高度的比例
  attempt: 3 // 尝试加载次数
});

方法二 IntersectionObserve()

API:IntersectionObserver - Web API 接口参考 | MDN

原理

Intersection Observer 是是浏览器原生提供的构造函数,使用它能省到大量的循环和判断。这个构造函数的作用是它能够观察可视窗口与目标元素产生的交叉区域。简单来说就是当用它观察我们的图片时,当图片出现或者消失在可视窗口,它都能知道并且会执行一个特殊的回调函数,就可以利用这个回调函数实现需要的操作。

tips:快速滚动无法触发

IntersectionObserver 的实现,应该采用 requestIdleCallback(),即只有线程空闲下来,才会执行观察器。这意味着,这个观察器的优先级非常低,只在其他任务执行完,浏览器有了空闲才会执行

交叉监视器的异步执行基于 event-loop,处于 动画帧回调函数 requestAnimationFrame 之后,又在 requestidlecallback 之前。大部分设备中浏览器的刷新频率为 60FPS ,大概是 16.6ms 一次,也就是说如果我们拖动滚动条的速度快于这个更新频率 IntersectionObserver 确实是有可能不会执行到的

hook

vue3 中选择用 hook 进行集成

useLazyload.ts 文件

// 定义自定义指令
const defineDirective = (app: any) => {
  app.directive('lazy', {
    mounted(el: HTMLImageElement, bindings: any) {
      // el表示使用指令的DOM元素
      // 指令的功能:实现图片的懒加载
      // 1、监听图片是否进入可视区
      const observer = new IntersectionObserver(([{ isIntersecting }]) => {
        // true;进入可视区域,false:未进入可视区域
        if (isIntersecting) {
          // 1、给图片的src属性赋值图片的地址
          el.src = bindings.value;
          // 2、取消图片的监听,默认是会一直监听的,如果不取消。就会一直执行
          // eslint-disable-next-line spellcheck/spell-checker
          observer.unobserve(el);
        }
      });
      // 监听dom元素
      observer.observe(el);
    }
  });
};
export default {
  install(app: any) {
    // 自定义指令
    defineDirective(app);
  }
};

基本用法

main.ts

import lazy from './hooks/useLazy';

app.use(lazy);

对需要进行懒加载的 img 标签,直接将:src替换为v-lazy

    <img
      class="image"
      :key="isCI ? data.rendered_result_image_url : data.rendered_standard_image_url"
      v-lazy="isCI ? data.rendered_result_image_url : data.rendered_standard_image_url"
    />

注意:若图片为循环渲染、分页显示,则必须写 key 值,不然切换页面后页面视图不刷新

方法三 loading="lazy"

HTML 新增 loading 属性

基本用法

<img src="xxx.png" loading="lazy">

loading 属性支持 3 种属性值:

  • auto 浏览器默认的懒加载策略,和不增加这个属性的表现一样
  • lazy 在资源距当前视窗到了特定距离内后再开始加载
  • eager 立即加载,无论资源在页面中什么位置

图片必须声明 width 和 height,不然会看到布局发生移动

兼容性

https://caniuse.com/?search=loading

方法四 滚动监听+scrollTop+offsetTop+innerHeight

如何判断元素是否到达可视区域

  • window.innerHeight 是浏览器可视区的高度;
  • document.body.scrollTop || document.documentElement.scrollTop是浏览器滚动的过的距离;
  • imgs.offsetTop 是元素顶部距离文档顶部的高度(包括滚动条的距离);
  • 内容达到显示区域: img.offsetTop < window.innerHeight + document.body.scrollTop;
  • 到达可视区域后,imgs[i].src = imgs[i].getAttribute('data-src') as string;将 data-src 属性值赋值给 src,实现懒加载

但在集成过程中发现,由于 img 父元素标签没有设置高度,scrollTop始终为 0,无法实现动态计算window.innerHeight + document.body.scrollTop的值

方法五:滚动监听+getBoundingClientRect()

API:Element.getBoundingClientRect() - Web API 接口参考 | MDN

//获取所有img标签
const imgs = document.getElementsByTagName('img');

onMounted(() => {
   //用于首屏加载
  lazyLoad();
  //添加滚动事件监听
  document.addEventListener('scroll', throttle(lazyLoad, 500), true);
});
onUnMounted(() => {
  document.removeEventListener('scroll', throttle(lazyLoad, 500), true);
});

// 节流
const throttle = (fn: { apply: (arg0: any, arg1: any[]) => void }, t: number) => {
  let flag = true;
  const interval = t || 500;
  return function (this: any, ...args: any) {
    if (flag) {
      fn.apply(this, args);
      flag = false;
      setTimeout(() => {
        flag = true;
      }, interval);
    }
  };
};

const lazyLoad = () => {
  const offsetHeight = window.innerHeight || document.documentElement.clientHeight;
  Array.from(imgs).forEach(item => {
    const oBounding = item.getBoundingClientRect(); //返回一个矩形对象,包含上下左右的偏移值
    if (0 <= oBounding.top && oBounding.top <= offsetHeight) {
      //     //性能优化 进行判断 已经加载的不会再进行加载
      if (item.getAttribute('alt') !== 'loaded') {
        item.setAttribute('src', item.getAttribute('data-src') as string);
        item.setAttribute('alt', 'loaded');
      }
    }
  });
};

缺点

  • 挂载时需要立刻调用 lazyLoad 函数,不然首屏不会加载;若需要图片分页,切换分页后不会也主动加载,需要调用 lazyLoad 方法
  • 同样需要给图片设置 key 值,不然分页图片不刷新