前言

通过 httparchive.org 的数据展示我们可以知道,当下网站大多数需求最多的资源类型还是图像,且它还比其他资源占用更多的带宽。 在图像居多的网站中,图像资源的加载对整体的用户体验有非常明显的影响,其中最常见的就是图片加载过慢。对此我们通常有这几种解决方案:图片优化,对图片进行压缩或者选择更加合适的图片格式、优化网络传输,如使用 CDN 加速以及优化图片的加载策略。
本文主要想聊的也就是「优化图片加载策略」

「图片优化」 和 「优化网络传输」 前者比较容易做到,后者多数情况都是靠砸钱优化的。

懒加载

懒加载策略就是:推迟加载非视口内的图片,从而减少资源的请求数量。

目前主流的懒加载方案有以下几种:

img 标签 loading

从 chrome 76+ 版本开始,开发者可以使用 loading 属性来推迟加载,只有当页面滚动到离对应图片还有一定位置时才会进行加载。通过给 loading 属性设置 lazy 值,即可简单实现图片的懒加载。可以使用 caniuse.com 查询浏览器的兼容性支持。对于不支持 loading 属性的浏览器会自动忽略该属性,不会造成影响。

<img scr='image.jpg' loading='lazy' alt='测试图片'/>

Intersection Observer API

Intersection Observer API 通过异步观察目标元素与父级元素或顶级文档视口的位置变化。可以通过它来实现图片懒加载。

/*
<!-- 图片 DOM --> 
<img data-src="image1.jpg" />
<img data-src="image2.jpg" />
*/

// 监听器配置
const config = {
    // pass
}

// 创建监听器
const observer = new IntersectionObserver(function(item, self) {
    item.forEach(({ isIntersecting, target }) => {
        if(isIntersecting) {
            if(target.dataset.src) {
                target.src = target.dataset.src;
                target.removeAttribute('data-src');
            }
            self.unobserve(target);
        }
    })

}, config);

// 绑定监听器
const images = document.querySelectAll("[data-src]");
images.forEach((image) => {
    observer.observe(image);
})

scroll

因为 Intersection Observer API 是存在兼容性问题的,除了直接添加对应 polyfill 以外,我们还可以考虑降级为监听 scroll、resize、orientationchange 事件。具体实现思路和上面 Intersection Observer 差不多。具体点需要注意的是:计算图片节点与目标视口的距离,还需要配合使用节流函数,避免性能问题。

预加载

懒加载策略与懒加载相反:尽快地加载出图片,使得用户体验更为流畅。

link preload

使用硬编码 link 标签来实现预加载通常是最好的,但很可惜多数时候预加载的使用场景是动态的。

<link ref='preload' as='image' href='image1.jpg'/>

HTML 的 head 标签可以声明资源请求,指定页面需要预加载的资源,并且在浏览器的主要渲染机制启动之前加载,避免阻塞页面渲染且保证资源尽早可用。

动态加载

动态变化的场景下一般使用动态创建 img 标签或者动态发起 Ajax 请求。这种方法可能会导致资源加载顺序的问题,同时还有跨域问题需要注意。

// 动态加载图片
function loadImage(url){
    return new Promise((resolve, reject) => {
        let temp_img = new Image();
        temp_img.src = url;
        temp_img.onload = () => {
            resolve("load success");
        }
        temp_img.onerror = () => {
            reject("load error: " + temp_img.src);
        }
        temp_img = null;
    })
}

响应式加载

不同的用户设备和网络都是不同的,通常移动设备上对网络的要求更高,如果图片资源无差别使用相同图片,可能造成带宽浪费或者是图片不清晰以及视觉体验差的问题,因此我们需要根据不同的设备加载不同的图片。

这种通常可以使用 picture 标签来定义多个不同的 source 节点和一个 img 节点,用于提供图片在不同设备或场景下的表现。

<!-- 为不同的媒体条件裁剪或修改图像。 -->
<picture>
  <source srcset="image1_pc.png" media="(min-width: 990px)" />
  <source srcset="image1_mobile.png" media="(min-width: 750px)" />
  <img src="image1_def.png" alt="default" />
</picture>


<!-- 在支持的浏览器中优先使用更适合的图片格式 -->
<!-- 同时支持在有兼容性问题的浏览器中回退加载其他格式的图片。 -->
<picture>
  <source srcset="image1.webp" type="image/webp" />
  <source srcset="image1.jpg" type="image/jpeg" />
  <img src="image1.jpg" alt="default" />
</picture>

<!-- 通过按需加载并显示最适合用户设备的图像,从而节省带宽和加快页面加载时间。 -->
<picture>
  <source
    srcset="image1_pc.png, image1_pc_2x.png 2x"
    media="(min-width: 990px)"
  />
  <source
    srcset="image1_mobile.png, image1_mobile_2x.png 2x"
    media="(min-width: 750px)"
  />
  <img
    srcset="image1_def.png, image1_def_2x.png 2x"
    src="image1_def.png"
    alt="default"
  />
</picture>

HTTP/2

使用 HTTP/1.X 协议时,浏览器有同源最大并发连接数的限制,且 HTTP/1.X 不支持多路复用,因此一个多图站点想要获得较完整的视觉呈现,会有一定程度的延迟:所有的资源请求(包括图片资源)会进入资源队列等待被下载。

如果不使用 HTTP/2 可以使用以下这几种方案:

参考

HTTPArchive 数据网站 (无中文需翻译)

近期文章
title: Git 使用心得
time: 2025-02-17
tag: #Git
title: 虚拟DOM
time: 2025-01-08
tag: #前端#性能