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

本文主要思考分析如何在 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 中的图像

代码:

html 复制代码
<!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属性是无效的

代码:

html 复制代码
<!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 版:

html 复制代码
<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 版

ts 复制代码
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 提供的信息:

兼容性:

html 复制代码
<img src="image.jpg" loading="lazy">

使用开源组件库

React Lazy Load Image 组件

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

  1. 安装
js 复制代码
// NPM
$ npm i --save react-lazy-load-image-component
// Yarn
$ yarn add react-lazy-load-image-component
  1. 引入
ts 复制代码
import Image from "../images/bird.jpg"; // 图片资源
import { LazyLoadImage } from "react-lazy-load-image-component";
  1. 使用
ts 复制代码
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 即可

html 复制代码
<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 监听高分辨率图片加载完成,替换占位图片

html 复制代码
<!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 属性,并包含图片的值。

ts 复制代码
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 框架)

typescript 复制代码
<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 使用情况等因素来决定使用同步还是异步解码。
html 复制代码
<img src="example.jpg" decoding="async">

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

参考文章

相关推荐
YBN娜8 分钟前
Vue实现登录功能
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=8 分钟前
CSS——选择器、PxCook软件、盒子模型
前端·javascript·css
minDuck12 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
小政爱学习!33 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。38 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼44 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning1 小时前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人1 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架