前端性能优化系列 – 图片加载的优化策略篇

本文主要思考分析如何在 Web 页面里优化图片加载

策略一:图片压缩

从根本上解决就是提前对图片进行压缩,这是我对【市面上可视化的图像压缩工具】的调研结果:

平台/工具 类型 支持的图像格式 上传的单张图像限制大小 批量限制数 是否支持修改图像质量
Tinypng Web JEPG, PNG, GIF, WEBP 5M(付费版 75 M) 20 张(付费版无限制) 默认最佳质量
Tinypng 桌面端 – 破解版 Windows、Mac JEPG, PNG, GIF, WEBP 5M 无限制 默认最佳质量
shortpixel Web JEPG, PNG, GIF, WEBP 10 M(登录后 100 M) 50 张 默认最佳质量
Compressor.io Web JEPG, PNG, GIF, WEBP, SVG 10M(付费版 20 M) 10 张(付费版无限制) 默认最佳质量(付费版支持)
iloveimg Web JEPG, PNG, GIF, WEBP, SVG 5M 30 张 默认最佳质量
imageresizer Web JEPG, PNG, GIF, WEBP 100 M 50 张
Caesium Windows、Mac JEPG, PNG, GIF, WEBP 无限制 无限制
智图 Windows、Mac JEPG, PNG, GIF, WEBP 无限制 无限制
Win10 照片查看器 Windows JEPG, PNG, GIF, WEBP, ICO, TIFF, BMP 无限制 1 张

实测压缩效果

工具 JPG 图压缩后
(原 1.36MB)
PNG 图压缩后
(原 369 KB)
GIF 动图压缩后
(原 1.14 MB)
Tinypng 231 KB 70.6 KB
shortpixel 198 KB 58.7 KB 472 KB
Compressor.io 177 KB 65 KB 601 KB
iloveimg 361 KB 69 KB 658 KB
imageresizer 270 KB 47.8 KB
Caesium 236 KB 59.1 KB
智图 183 KB 48 KB
Win10 照片查看器 272 KB 292KB

更多工具及实测内容参考我另一篇文章:前端性能优化系列 – 图片压缩篇

策略二:响应式图片

根据设备分辨率提供不同尺寸的图片,例如在 4K 屏上,需要高分辨率的,但如果是像手机这样的小屏幕就只需要低分辨率的图片即可

需求:

  • 屏幕宽度 > 640 px,显示:large-1920w.jpg 高分辨率图(1.36 MB
  • 屏幕宽度 ≤ 640 px,显示:normal-960w.jpg 普通图(148 KB
  • 默认显示:normal-960w.jpg(出于兼容问题的考虑得加入默认图

前端性能优化系列 - 图片加载的优化策略篇

原生实现

使用 <picture> 元素(推荐)

兼容性:

前端性能优化系列 - 图片加载的优化策略篇

<picture> 元素可以让你根据不同的设备分辨率、窗口大小或者设备像素比来加载不同的图片。它需要搭配一个或多个 <source> 元素组合使用,每个 <source> 可以为一个特定的分辨率提供不同的图片。

<picture> 按照自上而下的 <source> 标签里的图片资源按序加载。一旦找到一个匹配的媒体查询,浏览器会立即停止继续查找,并加载对应 srcset 中的图像

代码:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style>
      body {
        margin: 0;
      }
      img {
        width: 100%;
        height: auto;
      }
    </style>
  </head>
  <body>
    <picture>
      <source media="(min-width: 641px)" srcset="./assets/large-1920w.jpg" />
      <source media="(max-width: 640px)" srcset="./assets/normal-960w.jpg" />
      <img src="./assets/normal-960w.jpg" />
    </picture>
  </body>
</html>

效果:

前端性能优化系列 - 图片加载的优化策略篇
前端性能优化系列 - 图片加载的优化策略篇

使用 srcset + sizes 属性

兼容性:

前端性能优化系列 - 图片加载的优化策略篇

参考文档:响应式图像教程

  1. srcset属性列出多张可用的图像,每张图像的 URL 后面加上宽度描述符,空格隔开

宽度描述符就是图像原始的宽度(单位 px),加上字符w。如:./assets/normal-960w.jpg 960w

  1. sizes属性列出不同设备的图像显示宽度

sizes属性的值是一个逗号分隔的字符串,除了最后一部分,前面每个部分都是一个放在括号里面的媒体查询表达式,后面是一个空格,再加上图像的显示宽度

例如想设置图像在宽度不超过640px的设备显示宽度为100vw,写作:(max-width: 640px) 100vw

  1. 浏览器根据当前设备的宽度,从sizes属性获得图像的显示宽度,然后从srcset属性找出最接近该宽度的图像,进行加载

假定当前设备的屏幕宽度是 425px,浏览器从sizes属性查询得到,图片的显示宽度是 100vw,即 425px。srcset属性里面,最接近此宽度为 960px 的图片,于是加载 normal-960w.jpg

注意,sizes属性必须与srcset属性搭配使用。单独使用sizes属性是无效的

代码:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style>
      body {
        margin: 0;
      }
    </style>
  </head>
  <body>
    <img
      srcset="./assets/normal-960w.jpg 960w, ./assets/large-1920w.jpg 1920w"
      sizes="(max-width: 640px) 100vw, 100vw"
      src="./assets/normal-960w.jpg"
    />
  </body>
</html>

此方法不推荐使用,原因是:

  1. 媒体查询条件的判定不准确,例如代码里的 640 px 跟边界值不准确(也可能是我代码有问题或实测方法有问题,欢迎在评论区指出)
  2. 屏幕视口尺寸发生变化时不会自动切换图片

vue3 / React 实现

实现思路:

  1. 先写一个 img 元素,通过变量 imageSrc 传入
  2. 通过 window.innerWidth 获取设备屏幕宽度(单位:px)
  3. 添加屏幕尺寸变化的监听器,当屏幕尺寸发生变化时调用改变变量 imageSrc的函数(主要针对移动端横竖屏切换)

vue3 版:

<template>
    <img :src="imageSrc" />
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    const imageSrc = ref('');
    const updateImageSrc = () => {
      imageSrc.value = window.innerWidth <= 1200 ? './assets/small.jpg' : './assets/large.jpg';
    };
    onMounted(() => {
      window.addEventListener('resize', updateImageSrc);
      updateImageSrc();
    });
    onUnmounted(() => {
      window.removeEventListener('resize', updateImageSrc);
    });
    return {
      imageSrc,
    };
  },
};
</script>

React 版

import React, { useState, useEffect } from 'react';

const ResponsiveImage = () => {
  const [imageSrc, setImageSrc] = useState('');
  const updateImageSrc = () => {
    setImageSrc(window.innerWidth <= 1200 ? './assets/small.jpg' : './assets/large.jpg');
  };
  useEffect(() => {
    window.addEventListener('resize', updateImageSrc);
    updateImageSrc();
    return () => {
      window.removeEventListener('resize', updateImageSrc);
    };
  }, []);
  return <img src={imageSrc} />;
};

export default ResponsiveImage;

策略三:图片懒加载

图片懒加载:只在图片出现视口区域时才进行加载

原生实现

img 标签的 loading 属性

参考 img 标签之 loading 属性 – MDN 提供的信息:

前端性能优化系列 - 图片加载的优化策略篇

兼容性:

前端性能优化系列 - 图片加载的优化策略篇

<img src="image.jpg" loading="lazy">

使用开源组件库

React Lazy Load Image 组件

React Lazy Load Image 组件(React 框架):支持图片懒加载以及自定义加载占位内容

  1. 安装
// NPM
$ npm i --save react-lazy-load-image-component
// Yarn
$ yarn add react-lazy-load-image-component
  1. 引入
import Image from "../images/bird.jpg"; // 图片资源
import { LazyLoadImage } from "react-lazy-load-image-component";
  1. 使用
import React from "react";
import Image from "../images/bird.jpg";
import { LazyLoadImage } from "react-lazy-load-image-component";

export default function App() {
  return (
    <div>
      <LazyLoadImage src={Image}
        width={600} height={400}
        alt="Image Alt"
      />
     </div>
  );
}

注意需要明确定义图片的宽和高,以避免累积布局偏移(CLS)问题

  1. 实践测试

传送门:Live 演示地址(来自文章如何在 React 中实现图片懒加载

打开谷歌浏览器控制台,停用缓存、网络设置为高速 3G

前端性能优化系列 - 图片加载的优化策略篇

设置完后刷新页面,往下滑:

前端性能优化系列 - 图片加载的优化策略篇

Element Plus 的 Image 组件

如果你的 vue 项目有引入 element plus,推荐采用此图片组件来做图片懒加载

Element Plus 的 Image 组件(vue 框架):图片容器,在保留所有原生 img 的特性下,支持懒加载,自定义占位、加载失败等

前端性能优化系列 - 图片加载的优化策略篇

传送门:Live 演示地址(Element Plus Playground)

使用的时候只需要在 el-image 组件设置 lazy 属性为 true 即可

<template>
  <div class="demo-image__lazy">
    <el-image v-for="url in urls" :key="url" :src="url" lazy />
  </div>
</template>

<script lang="ts" setup>
const urls = [
  'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
  'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
  'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
  'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
  'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
  'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
  'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg',
]
</script>

<style scoped>
.demo-image__lazy {
  height: 400px;
  overflow-y: auto;
}
.demo-image__lazy .el-image {
  display: block;
  min-height: 200px;
  margin-bottom: 10px;
}
.demo-image__lazy .el-image:last-child {
  margin-bottom: 0;
}
</style>

轻量级懒加载 JS 库

如果你的项目没有或不想使用体积较大的开源组件,那么推荐你采用能实现图片懒加载的轻量级 JS 库,例如:Lazy SizesLozad.js 等(传送门:分享九个 JavaScript 图片懒加载库 – 掘金

策略四:加载用低分辨率图占位

原生实现

使用原生 API onload 监听高分辨率图片加载完成,替换占位图片

<!DOCTYPE html>
<html>
<head>
<style>
  img {
    width: 100%;
    height: auto;
  }
</style>
</head>
<body>
  <picture>
    <source srcset="./assets/high-res.jpg" media="(min-width: 300px)">
    <img src="./assets/low-res-blurred.jpg">
  </picture>
  <script>
    const highResSource = document.querySelector('source');
    const imgElement = document.querySelector('img');

    // 加载高分辨率图片
    const highResImage = new Image();
    highResImage.src = highResSource.srcset;
    highResImage.onload = () => {
      // 替换低分辨率图片为高分辨率图片
      imgElement.src = highResImage.src;
    };
  </script>
</body>
</html>

开源组件库

React Lazy Load Image 组件

React Lazy Load Image 组件(React 框架):支持图片懒加载以及自定义加载占位内容

安装、引入相关步骤上面提到过了,此处不赘述,使用上加入低分辨率图作为加载中的占位,可以参考 前端性能优化系列 – 图片压缩篇 介绍的可控制压缩质量的工具

如采用 squoosh 图片压缩工具原图进行压缩:原图大小为 286KB,压缩后的低分辨率图大小为2.5KB

前端性能优化系列 - 图片加载的优化策略篇

使用占位符图片需要在图片添加一个 PlaceholderSrc 属性,并包含图片的值。

import { LazyLoadImage } from 'react-lazy-load-image-component';
import Image from "../images/bird.jpg";
import PlaceholderImage from "../images/placeholder.jpg";

<LazyLoadImage
    src={Image}
    PlaceholderSrc={PlaceholderImage}
    width={600}
    height={400}
/>

传送门:Live 演示地址

同上打开谷歌浏览器控制台,停用缓存、网络设置为高速 3G

前端性能优化系列 - 图片加载的优化策略篇

大大减少了首屏加载资源的大小,从而大大提升首屏加载速度

Ant Design 的 Image 组件

Ant Design 的 Image 组件(React 框架)

前端性能优化系列 - 图片加载的优化策略篇

<Image
    width={200}
    src="高分辨率原图链接"
    placeholder={
        <Image
            preview={false}
            src="低分辨率图链接"
            width={200}
        />
    }
/>

Ant Design 的 Image 组件也可能实现这样的渐进式加载,将高分辨率原图(1.4MB)直接放在 src 字段里,同时借助 placeholder 字段设置加载占位放置提前压缩好的低分辨率图(52.5KB)

策略五:可视区域渲染

懒加载是等用户滑动到对应区域时才去加载图片,其实还可以做得更彻底,那就是没出现在可视区域的内容,直接不渲染了

默认情况下,浏览器会渲染所有内容,但为了实现更快的初始加载时间和更高的滚动性能。可以content-visibility 属性来控制元素的渲染时机

  • visible:默认值,元素及其子元素按照正常的渲染模式进行渲染
  • hidden:元素及其子元素不会被渲染。这类似于 display: none,但与 visibility: hidden 不同,因为它不会保留元素的布局空间
  • auto:浏览器会自动确定元素及其子元素是否应该被渲染。这通常意味着,如果元素在屏幕外(即在当前视口之外),则浏览器会跳过渲染该元素及其子元素,从而节省渲染时间。当元素进入视口时,浏览器会自动渲染它

兼容性:(75.29%,兼容性较差,需融合其他策略)

前端性能优化系列 - 图片加载的优化策略篇

content-visibility 属性的主要优点是它可以显著提高网页的性能,特别是对于包含大量内容和元素的网页。通过将 content-visibility 设置为 auto,可以让浏览器仅在需要时才渲染元素,从而减少页面加载时间和提高滚动性能

策略六:异步解码

图片传输到浏览器上要想展示,还需要经过解码。除了硬件本身外,如何解码直接影响着图片展示在用户眼前的时机

img 标签的 decoding 属性用于指定浏览器图片解码方式:

  • sync:同步解码。浏览器在主线程立即解码图片。这可能会阻塞其他任务,例如页面布局和绘制,直到图片解码完成。如果你有一个小的图片,或者图片在视觉上非常重要,选择这个值。
  • async:异步解码。浏览器在后台线程上解码图片,这样就不会阻塞其他任务。这对于大型图片或者不那么重要的图片比较有用,因为它们可能需要更长的时间来解码,而且不会影响到页面的其他部分。
  • auto:自动解码。默认值,让浏览器自己决定如何进行解码。浏览器可能会根据图片的大小当前的 CPU 使用情况等因素来决定使用同步还是异步解码。
<img src="example.jpg" decoding="async">

不过,跟延迟加载一样,异步解码需谨慎使用。因为它可能会导致 FOUCFlash of Unstyled Content)意为“无样式内容闪烁”。这是指在网页加载过程中,内容在应用样式之前短暂地显示出来,导致页面内容闪烁或跳动的现象。这种情况通常发生在 CSS 样式尚未加载完成时,浏览器已经开始渲染页面内容。

参考文章

原文链接:https://juejin.cn/post/7329514858664968255 作者:ALKAOUA

(0)
上一篇 2024年1月30日 下午4:00
下一篇 2024年1月30日 下午4:11

相关推荐

发表回复

登录后才能评论