图片懒加载

1.什么是懒加载

懒加载是一种网页优化技术,在页面滚动时,只加载可视区域内的图片。

2.为什么要用懒加载

试想页面有一个长列表图片需要展示,用户点击链接进入页面后加载图片列表,此时不光页面加载时间长,同时在同一时间内会向服务器发送大量请求,造成服务器压力过大。所以我们可以只加载用户可视范围内的图片,其余的图片在用户滚动到可视范围内时再加载。

懒加载优点:

  • 减少页面加载时间,优化用户体验
  • 减少请求,减轻服务器压力

3.实现原理

监听页面滚动,判断元素是否进入可视范围,进入则加载图片,否则显示占位图片:

  1. 判断元素是否在可视区域:
  • 我们使用 innerHeight 获取视口的高度, 使用 getBoundingClientRect()获取元素距离页面顶部的高度与元素底部距离页面顶部的高度:
  • getBoundingClientRect 图解:
    getBoundingClientRect
  • 当元素距离顶部的距离eleTop小于视口高度viewHeight时,元素在可视区域内,加载图片:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const lazyLoad = (elementSelect) => {
// 观察的元素
const eles = document.querySelectorAll(elementSelect);
// 可视区域高度 document.documentElement 获取document根元素 html, clientHeight获取可视区域高度(不包括滚动条) innerHeight获取可视区域高度(包括滚动条)
const viewHeight = document.documentElement.clientHeight;

// 判断元素是否在可视区域
eles.forEach((ele) => {
if (!ele.dataset.src) return;
// 元素距离文档顶部的高度
const eleTop = ele.getBoundingClientRect().top;
// 元素底部距离可视区域顶部的高度 大于0 在可视区域下 小于0 在可视区域上
const eleBottom = ele.getBoundingClientRect().bottom;
// 判断元素是否在可视区域 (元素距离顶部高度小于视口高度, 此时元素再可视范围内)
if (eleTop < viewHeight && eleBottom > 0) {
// 加载图片
ele.src = ele.dataset.src;
}
});
};
  1. 监听滚动

我们已经知道元素是否在可视范围内,只需在滚动时判断元素是否在可视范围内即可,我们可以使用 window.onscroll 监听滚动事件,并添加防抖优化性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 防抖
const debounce = (fn, delay) => {
let timer = null;
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, arguments), delay);
};
};
window.addEventListener(
"scroll",
debounce(() => {
lazyLoad("img");
}, 100)
);

在页面初始化时我们还需要判断一次元素是否在可视范围,以下是实现效果:

JS 完整代码:

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
// 手写懒加载结合防抖
const lazyLoad = (elementSelect) => {
// 观察的元素
const eles = document.querySelectorAll(elementSelect);
// 可视区域高度 document.documentElement 获取document根元素 html, clientHeight获取可视区域高度(不包括滚动条) innerHeight获取可视区域高度(包括滚动条)
const viewHeight = document.documentElement.clientHeight;

// 判断元素是否在可视区域
eles.forEach((ele) => {
if (!ele.dataset.src) return;
// 元素距离文档顶部的高度
const eleTop = ele.getBoundingClientRect().top;
// 元素底部距离可视区域顶部的高度 大于0 在可视区域下 小于0 在可视区域上
const eleBottom = ele.getBoundingClientRect().bottom;
// 判断元素是否在可视区域
if (eleTop < viewHeight && eleBottom > 0) {
// 加载图片
ele.src = ele.dataset.src;
}
});
};

// 防抖
const debounce = (fn, delay) => {
let timer = null;
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, arguments), delay);
};
};

4.使用 IntersectionObserver 实现懒加载

IntersectionObserver构造函数实现懒加载更加简单,元素在可视范围内时IntersectionObserver对象属性isIntersecting为 true,我们可以直接了当的判断元素是不是在可视范围内,而不用再监听页面滚动来判断元素是否再可视范围内,但是低版本的浏览器不支持该构造函数

  1. 创建 IntersectionObserver 实例

    IntersectionObserver 接收两个参数:

    • callback: 当元素可见比例超过指定阈值后,会调用一个回调函数,此回调函数接受两个参数:

      entries:
      一个 IntersectionObserverEntry 对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。

      observer:
      被调用的 IntersectionObserver 实例。

    • options: 配置对象,可以设置以下属性:

      root: 监听元素的祖先元素 Element 对象,其边界盒将被视作视口。目标在根的可见区域的任何不可见部分都会被视为不可见。

      rootMargin:
      一个在计算交叉值时添加至根的边界盒 (bounding_box) 中的一组偏移量,类型为字符串 (string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和 CSS 中的 margin 属性等同; 可以参考 intersection root 和 root margin 来深入了解
      margin 的工作原理及其语法。默认值是”0px 0px 0px 0px”。

      threshold:
      规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组 0.0 到 1.0 之间的数组。若指定值为 0.0,则意味着监听元素即使与根有 1 像素交叉,此元素也会被视为可见。若指定值为 1.0,则意味着整个元素都在可见范围内时才算可见。可以参考阈值来深入了解阈值是如何使用的。阈值的默认值为 0.0。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const observer = (ele) => {
    const eles = document.querySelectorAll(ele);
    const observers = new IntersectionObserver(
    (entries, observer) => {
    entries.forEach((entry) => {
    if (entry.isIntersecting) {
    // 加载图片
    entry.target.src = entry.target.dataset.src;
    observer.unobserve(entry.target);
    }
    });
    },
    { threshold: 1 }
    );

    eles.forEach((item) => {
    observers.observe(item);
    });
    };
  2. 使用 IntersectionObserver 实例监听元素

    实现效果:

    JS 完整代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 创建实例
    // 使用IntersectionObserver API 实现懒加载
    const observer = (ele) => {
    const eles = document.querySelectorAll(ele);
    const observers = new IntersectionObserver(
    (entries, observer) => {
    entries.forEach((entry) => {
    if (entry.isIntersecting) {
    // 加载图片
    entry.target.src = entry.target.dataset.src;
    observer.unobserve(entry.target);
    }
    });
    },
    { threshold: 1 }
    );

    eles.forEach((item) => {
    observers.observe(item);
    });
    };
    observer("img");

参考


图片懒加载
https://www.zphl.top/posts/lazyload.html
作者
lzp
发布于
2024年6月16日
许可协议