放肆青春的博客
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)

放肆青春

一个前端菜鸟的技术成长之路
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)
  • 技术汇总

    • 技术总结
    • 技术文章
  • 技术术语

  • 技术方案

    • 技术场景汇总
    • 技术方案

    • 前端方案

    • 三方平台

    • 图片方案

      • 图片
      • 图片懒加载
        • 图片懒加载
          • 优点
          • 原理
        • 图片懒加载方案
          • 1.位置计算+scroll+DataSet
          • 2.getBoundingClientRect + scroll+DataSet
          • 3. IntersectionObserver
          • 4.基于 img 标签的 loading 属性实现懒加载
          • 方案总结
    • 文件方案

  • 技术点

  • 设计模式及原则

    • 设计模式
    • 创建型模式

    • 结构型模式

    • 行为型模式

    • 设计原则
  • technology
放肆青春
2021-12-22

图片懒加载

# 图片懒加载

# 优点

提高前端性能,按需加载图片减轻服务器负担,提高页面加载速度。

# 原理

图片的加载是依赖于 src 路径,我们可以设置一个暂存器,把图片路劲放到暂存器中,当我们需要这个图片加载显示(进入可视区域)时,再把路径赋值给 src,这样就能实现按需加载,也就是懒加载。我们通常使用 html5 中的 data-属性作为暂存器,例如 src 的值默认是正在加载中的 GIF,而真正的图片路径是保存在 data-中。

问题拆分:

  1. 如何判断图片出现在了当前视口 (即如何判断我们能够看到图片)

(1)window.scroll 监听 Element.getBoundingClientRect() 并使用 throttle 节流

(2)IntersectionObserver(不支持 IE):一个能够监听元素是否到了当前视口的事件,一步到位!

const observer = new IntersectionObserver((changes) => {
  // changes: 目标元素集合
  changes.forEach((change) => {
    // intersectionRatio
    if (change.isIntersecting) {
      const img = change.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
});

observer.observe(img);
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 如何控制图片的加载

首先设置一个临时 Data 属性 data-src,控制加载时使用 src 代替 data-src,可利用 DataSet API 实现img.src = img.datset.src

data-*全局属性 是一类被称为自定义数据属性的属性,它赋予我们在所有 HTML 元素上嵌入自定义数据属性的能力,并可以通过脚本在 HTML 与 DOM 表现之间进行专有数据的交换。

# 图片懒加载方案

# 1.位置计算+scroll+DataSet

document.documentElement.clientHeight //获取屏幕可视区域的高度

document.documentElement.scrollTop //获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离

element.offsetTop //获取元素相对于文档顶部的高度

如果:clientHeight + scroolTop > offsetTop,则图片进入了可视区内,则被请求。

const imgs = document.getElementsByTagName("img");
const viewHeight = document.documentElement.clientHeight; // 视图高度
let count = 0; // 计数器,表示当前更新了几个,也可以作为下一个要更新元素的下标
const noop = () => {}; // 空函数

let lazyLaodWhtiThrottle = noop; // 带有节流功能的懒加载,初始化

// 懒加载
const lazyLaod = () => {
  if (count >= imgs.length) {
    // 加载完,移除事件
    window.removeEventListener("scroll", lazyLaodWhtiThrottle);
  }
  const scrollTop =
    document.documentElement.scrollTop || document.body.scrollTop; // 滚动距离

  // 判断每个图片是否在视图内
  // 是的话则加载真正的图片,count 加 1
  // 遍历超出视图底部的元素则跳出循环,没有必要再找下去
  // 如果图片没有 data-src, 则跳过,count 加 1
  for (let i = count; i < imgs.length; i++) {
    const img = imgs[i];
    const dataSrc = img.getAttribute("data-src");
    // 视图以下元素在当前不再遍历
    if (img.offsetTop > scrollTop + viewHeight) {
      break;
    }
    // 没有 data-src,不参与懒加载,程序应该跳过
    if (!dataSrc) {
      count++;
      continue;
    }
    img.removeAttribute("data-src");
    img.src = dataSrc;
    count++;
  }
};

// 使用节流
lazyLaodWhtiThrottle = _.throttle(lazyLaod, 200);
window.addEventListener("scroll", lazyLaodWhtiThrottle);

lazyLaod(); // 初始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 2.getBoundingClientRect + scroll+DataSet

Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。

const imgs = document.getElementsByTagName("img");
let count = 0; // 计数器,表示当前更新了几个,也可以作为下一个要更新元素的下标
const noop = () => {}; // 空函数
let lazyLaodWhtiThrottle = noop; // 带有节流功能的懒加载,初始化

const lazyLaod = () => {
  if (count >= imgs.length) {
    window.removeEventListener("scroll", lazyLaodWhtiThrottle);
  }
  for (let i = count; i < imgs.length; i++) {
    const img = imgs[i];
    const dataSrc = img.getAttribute("data-src");

    // 获取相对于视图窗口顶部的距离
    const { top } = img.getBoundingClientRect();

    // 如果大于可视区域高度,说明在视图窗口之下,跳出
    if (top > document.documentElement.clientHeight) {
      break;
    }
    // 不参与懒加载的元素, count + 1,进入下一个循环
    if (!dataSrc) {
      count++;
      continue;
    }
    img.removeAttribute("data-src");
    img.src = dataSrc;
    count++;
  }
};
lazyLaodWhtiThrottle = _.throttle(lazyLaod, 200);
window.addEventListener("scroll", lazyLaodWhtiThrottle);
lazyLaod();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 3. IntersectionObserver

const imgs = document.getElementsByTagName("img");

function lazyLaod() {
  const observer = new IntersectionObserver((res) => {
    for (let i = 0; i < res.length; i++) {
      const currentChange = res[i];
      const { target } = currentChange;
      const dataSrc = target.getAttribute("data-src");
      if (!currentChange.isIntersecting) continue;
      if (!dataSrc) {
        observer.unobserve(target);
        continue;
      }
      target.src = dataSrc;
      target.removeAttribute("data-src");
      observer.unobserve(target);
    }
  });

  for (let i = 0; i < imgs.length; i++) {
    observer.observe(imgs[i]);
  }
}

lazyLaod();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

代码解析:

  1. 实例化一个 IntersectionObserver 实例,注册回调函数。

  2. for 循环遍历所有图片,进行观察

  3. 针对回调的结果集做判断和处理,最后记得要注销改元素的观察

# 4.基于 img 标签的 loading 属性实现懒加载

前面讲的三种方案都是基于 javaScript 来进行元素位置判断,动态替换 src 来实现图片懒加载的效果。那么,有没有一种办法可以更加干脆,而不用这么多逻辑判断,并且性能更佳。答案是,有的。它就是 img 标签上原生支持的属性 loading。

我们可以很方便的使用 loading="lazy" 的方式来实现一个图片懒加载的功能,并且不依赖我们的 javaScript, 仅仅依靠原生支持以及浏览器的优化,性能上自然不必多说。

然而,非常遗憾的是,在 chrome 上,也在 75 版本之后才有支持,兼容性简直令人望而却步。因此,如果我们想要首先使用原生支持的 lazy-loading, 依然需要配合 javaScript 做一个可用性判断,以决定当前程序要采用何种懒加载方式。

这里我们可以采用两种方式,第一种就是丝毫不借助 javaScript,上来就是干,只需要在 img 标签上加上 loading="lazy" 即可。

<img src="https://xxxx/xxx.png" loading="lazy" />

当然,因为兼容性的问题,我们也可以在 avaScript 中做一层判断,这样,我们可以采用前面几种方案相同 HTML 结构, 依然给 img 标签赋予 src 和 data-src 两个资源。

const imgs = document.getElementsByTagName("img");

function lazyLaod() {
  // 判断是否支持 loading
  if ("loading" in HTMLImageElement.prototype) {
    for (let i = 0; i < imgs.length; i++) {
      const img = imgs[i];
      const dataSrc = img.getAttribute("data-src");
      if (!dataSrc) continue;
      img.loading = "lazy";
      img.src = dataSrc;
      img.removeAttribute("data-src");
    }
  }
}
lazyLaod();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

代码解析:

  1. 添加 loading = "lazy"

  2. 将 data-src 赋值给 src

# 方案总结

针对以上 4 种方案,在性能体验以及兼容性上各有利弊。或许我们惊喜于原生支持的方案,免去了很多繁杂写法和拥有更优的性能体验,但兼容性却不容乐观,相反,以往看似不过太靠谱的方案在兼容性方面却显得不错。如果我们的程序希望优先选择性能最佳的懒加载方式,完全可以通过策略模式,在当前浏览器环境使用中判断属性支持程度以动态切换懒加载策略,在支持度不高的浏览器中也能降级为兼容性方案。

本文细数了 4 种前端图片懒加载的实现方案,其中,位置计算类型的共通思想都是计算元素相对于可视区域的位置,只不过基于 offsetTop + clientHeight + scrollTop 的方案中,位置的计算借助了元素的偏移距离、文档的滚动距离以及可视区域高度,而基于 getBoundingClientRect 的方案则是通过这个 API 来获取相对于视口左上角的位置。两者相比,后者更加清晰、简单。

而 Intersection Observer API 的方案,避免了前两种方案位置计算的步骤,只在交集检测的结果集进行操作,当交集变化的时候,回调函数将会被调用,通过判断结果集 isIntersecting 属性即可。

最后一个方案,则是基于 img 标签原生支持的功能,通过设置 loading="lazy" 来实现图片懒加载,不需要借助 js 计算,仅仅依靠原生支持以及浏览器优化,性能更佳。但是兼容性方面目前来讲非常糟糕。


参考:前端图片懒加载指北:细数各种方案的实现和对比 (opens new window)

更新时间: 1/14/2022, 6:21:39 PM
图片
文件上传基础

← 图片 文件上传基础→

最近更新
01
前端权限管理
02-24
02
vue2指令
02-24
03
vue2 hook
02-24
更多文章>
Theme by Vdoing | Copyright © 2019-2022 放肆青春
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式