懒加载和预加载

懒加载和预加载是前端优化网页性能的重要手段之一。不止可以用于图片,其他资源(例如:css/js 文件,音/视频等)也可以使用。本文只讨论图片的预加载和懒加载,只有在合理的需求场景中合理的运用这两项技术,才能够有效地减少页面加载时间,提高用户体验。

什么是懒加载

懒加载又称延迟加载,是指页面滚动到某个位置时再加载该位置下的图片。对于那些不在用户视野内的图片,可以先不进行加载,避免对网页性能造成不必要的影响。它的优点在于能够减少页面加载的初始时间,常用于一个页面包含大量图片的场景,特别是当页面需要滚动查看内容时,例如电商平台的商品页。

图片懒加载的实现方法 🥊

实现懒加载通常是把 img 标签的 src 属性先用一个较小的占位图片代替,用一个自定义属性来存储真实的图片地址。当图片进入可视区域时再将图片的真实地址赋给 src 属性即可。下面是一个简单的代码示例 👇:

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
<body>
<img src="占位图片地址" data-src="真实图片地址" />

<script>
// 创建 IntersectionObserver 实例对象
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
// 当图片进入视口时,将其 data-src 属性值赋给 src 属性,触发加载
img.src = img.dataset.src
// 加载完成后,取消监听
observer.unobserve(img)
}
})
})

// 获取所有需要进行懒加载的图片元素
const lazyImages = document.querySelectorAll('img[data-src]')
// 遍历每个需要进行懒加载的图片元素 监听图片是否进入视口
lazyImages.forEach(img => {
observer.observe(img)
})
</script>
</body>

在上述代码中我们使用了 IntersectionObserver 接口来观察图片是否进入视口,当图片进入视口时,将图片的 data-src 赋给 src 属性,完成图片加载。如果对 IntersectionObserver 接口不熟悉请查看 MDN - IntersectionObserver

IntersectionObserver 接口是 ES6 中新增的 API,目前所有主流浏览器都已支持。在那之前我们通常监听滚动事件来实现图片懒加载,下面是一个 js 代码示例 👇:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function lazyLoad() {
// 获取所有需要进行懒加载的图片元素
var images = document.querySelectorAll('img[data-src]')
images.forEach(img => {
var rect = img.getBoundingClientRect()
// 判断图片是否进入视口
if (rect.top < window.innerHeight) {
// 将其 data-src 属性值赋给 src 属性,触发加载
img.src = img.dataset.src
// 移除 data-src 属性
img.removeAttribute('data-src')
}
})
}

// 先调用一次 加载首屏图片
lazyLoad()
// 监听滚动事件
window.addEventListener('scroll', lazyLoad)

在上述代码中,我们监听窗口的滚动事件,当用户滚动页面时,会依次遍历所有含有 data-src 属性的 img 元素,判断其是否进入了可视区域,如果是则将其 data-src 属性的值赋给 src 属性,完成图片的加载。

需要注意的是,该方法可能会在页面滚动过程中频繁地调用,可能会造成资源损耗,需要一定的手段进行优化。因此如果不是为了兼容老旧浏览器建议使用 IntersectionObserver 接口方法,而不是监听 scroll 事件。

什么是预加载

预加载是指在页面加载完成之前,提前加载所有需要使用的图片。它的优点在于在用户需要使用图片时能够立即展示,避免了等待加载的时间。常用于需要快速展示关键图片的场景,例如轮播图,游戏场景等。

图片预加载的实现方法 🥊

图片预加载原理就是让浏览器提前加载后面可能会用到的图片,浏览器会缓存已经加载过的资源,等后面用到的时候就能直接使用缓存的资源。

常用的方法有:

  • 使用 css 设置 background-image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <style>
    .preload {
    // 隐藏预加载图片的元素
    position: absolute;
    left: -9999px;
    }
    .img1 {
    // 设置背景 预加载图片
    background: url('预加载图片地址');
    }
    .img2 {
    background: url('预加载图片地址');
    }
    .img3 {
    background: url('预加载图片地址');
    }
    </style>
    <body>
    <ul class="preload">
    <li class="img1"></li>
    <li class="img2"></li>
    <li class="img3"></li>
    </ul>
    </body>
  • 使用 img 标签设置 src 属性;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <style>
    .preload {
    position: absolute;
    left: -9999px;
    }
    </style>
    <body>
    <ul class="preload">
    <img src="预加载图片地址" />
    <img src="预加载图片地址" />
    <img src="预加载图片地址" />
    </ul>
    </body>
  • 使用 js 的 Image 构造函数(推荐);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const images = ['预加载图片地址', '预加载图片地址', '预加载图片地址']

    function preload() {
    images.forEach(src => {
    const img = new Image()
    img.src = src
    })
    }

    window.addEventListener('load', preload)

    new Image() 的作用和 document.createElement('img') 一样,用来创建一个 img 元素,当为这个 img 元素的 src 属性提供值后,浏览器就会请求 src 所指向的资源。

    更推荐使用该方法是因为它不需要多余的 HTML 标签和 CSS 代码,而且更方便对图片的加载进行控制,比如需要按顺序预加载图片:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let n = 0
    function preload() {
    if (n >= images.length) {
    return
    }
    const img = new Image()
    img.onload = preload
    img.onerror = preload
    img.src = images[n++]
    }

    window.addEventListener('load', preload)

总结

前端图片的懒加载和预加载是优化网页性能的重要手段。

懒加载能够减少页面的初始加载时间。

预加载能够避免用户等待加载图片的时间,立即展示需要使用的图片。

两种方法各有各自的实用场景,合理的选择才能达到优化的目的。