自动化压缩和编码

使生成高性能图片源成为您开发流程的无缝组成部分。

本课程中的所有语法(从图片数据编码到为响应式图片提供支持的信息密集型标记)都是机器与机器通信的方法。您已经发现了客户端浏览器向服务器传达其需求以及服务器做出相应响应的多种方法。响应式图片标记(特别是 srcsetsizes)设法用相对较少的字符描述了惊人的信息量。无论好坏,这种简洁性都是经过设计的:使这些语法不那么简洁,从而更容易让开发人员解析,可能会使浏览器更难解析。添加到字符串的复杂性越高,解析器错误或不同浏览器之间行为意外差异的可能性就越大。

An automated image encoding window.

然而,使这些主题感觉如此令人生畏的相同特征也可以为您提供解决方案:机器容易读取的语法也是机器更容易写入的语法。作为 Web 用户,您几乎肯定遇到过许多自动化图片编码和压缩的示例:通过社交媒体平台、内容管理系统 (CMS) 甚至电子邮件客户端上传到 Web 的任何图片几乎总是会通过一个系统,该系统会调整大小、重新编码和压缩它们。

同样,无论是通过插件、外部库、独立的构建流程工具,还是负责任地使用客户端脚本,响应式图片标记都非常适合自动化。

这些是围绕自动化图片性能的两个主要关注点:管理图片的创建(它们的编码、压缩以及您将用于填充 srcset 属性的备用源),以及生成面向用户的标记。在本模块中,您将了解几种管理图片作为现代工作流一部分的常用方法,无论是作为开发流程中的自动化阶段,还是通过为您的站点提供支持的框架或内容管理系统,或者几乎完全由专用内容分发网络抽象出来。

自动化压缩和编码

您不太可能发现自己处于可以花时间手动确定用于项目中的每个单独图片的理想编码和压缩级别的境地,您也不希望这样做。正如尽可能减小图片传输大小非常重要一样,微调压缩设置并为要用于生产网站的每个图片资源重新保存备用源会在您的日常工作中引入巨大的瓶颈。

正如您在阅读各种图片格式和压缩类型时了解到的那样,图片最有效的编码始终取决于其内容,并且正如您在响应式图片中了解到的那样,图片源所需的备用尺寸将取决于这些图片在页面布局中所占据的位置。在现代工作流中,您将整体而不是单独地处理这些决策,为图片确定一组合理的默认值,以最好地适应它们要使用的上下文。

在为照片图片目录选择编码时,AVIF 在质量和传输大小方面显然是赢家,但支持有限,WebP 提供了优化的现代后备方案,而 JPEG 是最可靠的默认方案。我们需要为旨在占据页面布局侧边栏的图片生成的备用尺寸与旨在在我们最高的断点处占据整个浏览器视口的图片所需的尺寸差异很大。压缩设置将需要关注跨多个结果文件的模糊和压缩伪影,从而减少从每个图片中抠出每个可能的字节的空间,以换取更灵活和可靠的工作流。总而言之,您将遵循您从本课程中了解到的相同的决策过程,并将其放大。

至于处理本身,有大量的开源图片处理库提供了批量转换、修改和编辑图片的方法,它们在速度、效率和可靠性方面展开竞争。这些处理库将允许您一次将编码和压缩设置应用于整个图片目录,而无需打开图片编辑软件,并且以保留原始图片源的方式,以便在需要动态调整这些设置时使用。它们旨在在各种环境(从本地开发环境到 Web 服务器本身)中运行,例如,专注于压缩的 Node.js ImageMin 可以通过一系列 插件 进行扩展以适应特定应用程序,而跨平台 ImageMagick 和基于 Node.js 的 Sharp 开箱即用,功能之多令人震惊。

这些图片处理库使开发人员可以构建专门用于无缝优化图片的工具,作为标准开发流程的一部分,从而确保您的项目始终引用生产就绪的图片源,并尽可能减少开销。

本地开发工具和工作流

任务运行器和捆绑器(如 Grunt、Gulp 或 Webpack) 可用于优化图片资源以及其他常见的与性能相关的任务,例如 CSS 和 JavaScript 的缩小。为了说明这一点,让我们来看一个相对简单的用例:您项目中的一个目录包含十几个照片图片,这些图片旨在用于面向公众的网站。

首先,您需要确保这些图片的编码一致且高效。正如您在前几个模块中了解到的那样,WebP 在质量和文件大小方面是照片图片的高效默认格式。WebP 支持良好,但并非普遍支持,因此您还需要包含渐进式 JPEG 形式的后备方案。然后,为了利用 srcset 属性高效地交付这些资源,您需要为每种编码生成多个备用尺寸。

如果使用图片编辑软件完成此操作,这将是一项重复且耗时的繁琐工作,但 Gulp 等任务运行器旨在自动化正是这种重复操作。gulp-responsive 插件(它使用 Sharp)是众多选项之一,所有这些选项都遵循类似的模式:收集源目录中的所有文件,重新编码它们,并根据您在图片格式和压缩中了解到的相同标准化“质量”速记压缩它们。然后,将生成的文件输出到您定义的路径,准备在面向用户的 img 元素的 src 属性中引用,同时保持原始文件完好无损。

const { src, dest } = require('gulp');
const respimg = require('gulp-responsive');

exports.webp = function() {
  return src('./src-img/*')
    .pipe(respimg({
      '*': [{
        quality: 70,
        format: ['webp', 'jpeg'],
        progressive: true
      }]
  }))
  .pipe(dest('./img/'));
}

有了这样的流程,如果项目中的某人无意中将编码为大型真彩色 PNG 的照片添加到包含原始图片源的目录中,则不会对生产环境造成损害,无论原始图片的编码如何,此任务都将生成高效的 WebP 和可靠的渐进式 JPEG 后备方案,并且压缩级别可以轻松地动态调整。当然,此过程还确保您的原始图片文件将保留在项目的开发环境中,这意味着可以随时调整这些设置,而只会覆盖自动输出。

为了输出多个文件,您需要传递多个配置对象,除了添加 width 键和像素值之外,所有配置对象都相同

const { src, dest } = require('gulp');
const respimg = require('gulp-responsive');

exports.default = function() {
  return src('./src-img/*')
    .pipe(respimg({
    '*': [{
            width: 1000,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-1000' }
            },
            {
            width: 800,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-800' }
            },
            {
            width: 400,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-400' },
        }]
        })
    )
    .pipe(dest('./img/'));
}

在上面的示例中,原始图片 (monarch.png) 超过 3.3MB。此任务生成的最大文件 (monarch-1000.jpeg) 约为 150KB。最小的文件 monarch-400.web 仅为 32KB。

[10:30:54] Starting 'default'...
[10:30:54] gulp-responsive: monarch.png -> monarch-400.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-800.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-1000.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-400.webp
[10:30:54] gulp-responsive: monarch.png -> monarch-800.webp
[10:30:54] gulp-responsive: monarch.png -> monarch-1000.webp
[10:30:54] gulp-responsive: Created 6 images (matched 1 of 1 image)
[10:30:54] Finished 'default' after 374 ms

当然,您需要仔细检查结果中是否有可见的压缩伪影,或者可能增加压缩以节省更多空间。由于此任务是非破坏性的,因此可以轻松更改这些设置。

总而言之,为了换取您可以通过仔细的手动微优化抠出的几千字节,您获得了一个不仅高效而且有弹性的流程,该工具可将您对高性能图片资源的了解无缝地应用于整个项目,而无需任何手动干预。

实践中的响应式图片标记

填充 srcset 属性通常是一个简单的手动过程,因为该属性实际上只捕获您在生成源时已完成的配置信息。在上面的任务中,我们已经建立了我们的属性将遵循的文件名和宽度信息

srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w"

请记住,srcset 属性的内容是描述性的,而不是规范性的。只要每个源的纵横比一致,过载 srcset 属性就没有任何危害。srcset 属性可以包含服务器生成的每个备用剪切的 URI 和宽度,而不会导致任何不必要的请求,并且我们为渲染的图片提供的候选源越多,浏览器就越能有效地定制请求。

正如您在响应式图片中了解到的那样,您需要使用 <picture> 元素来无缝处理 WebP 或 JPEG 后备模式。在这种情况下,您将结合 type 属性和 srcset 属性使用。

<picture>
  <source type="image/webp" srcset="filename-1000.webp 1000w, filename-800.webp 800w, filename-400.webp 400w">
  <img srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w" sizes="…" alt="…">
</picture>

正如您所了解的,支持 WebP 的浏览器将识别 type 属性的内容,并选择该 <source> 元素的 srcset 属性作为图片候选列表。无法将 image/webp 识别为有效媒体类型的浏览器将忽略此 <source>,而是使用内部 <img> 元素的 srcset 属性。

在浏览器支持方面还需要考虑一个问题:不支持任何响应式图片标记的浏览器仍然需要后备方案,否则我们可能会在特别旧的浏览环境中冒着图片损坏的风险。由于 <picture><source>srcset 在这些浏览器中都被忽略,因此我们希望在内部 <img>src 属性中指定默认源。

由于向下缩放图片在视觉上是无缝的,并且 JPEG 编码得到普遍支持,因此最大的 JPEG 是明智的选择。

<picture>
  <source type="image/webp" srcset="filename-1000.webp 1000w, filename-800.webp 800w, filename-400.webp 400w">
  <img src="filename-1000.jpg" srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w" sizes="…" alt="…">
</picture>

sizes 可能有点难以处理。正如您了解到的sizes 在本质上是上下文相关的,如果不了解图片在渲染布局中要占据的空间量,就无法填充该属性。为了获得最有效的请求,准确的 sizes 属性需要在最终用户发出这些请求时位于我们的标记中,这远早于请求管理页面布局的样式。完全省略 sizes 不仅违反了 HTML 规范,而且会导致等效于 sizes="100vw" 的默认行为,从而告知浏览器此图片仅受视口本身约束,从而导致选择最大的候选源。

与任何特别繁琐的 Web 开发任务一样,已经创建了许多工具来抽象出手写 sizes 属性的过程。respImageLint 是一个绝对必要的代码片段,旨在审查您的 sizes 属性的准确性,并提供改进建议。它作为书签小程序运行,您在浏览器中运行时,指向包含图片元素的完全渲染的页面。在浏览器完全了解页面布局的环境中,它还将几乎以像素完美的精度了解图片在每个可能的视口大小下要占据的空间。

Responsive image report showing size/width mismatch.

用于 linting 您的 sizes 属性的工具当然很有用,但它作为批量生成它们的工具具有更大的价值。如您所知,srcsetsizes 语法旨在以视觉上无缝的方式优化图片资源的请求。虽然不应该在生产中使用,但在本地开发环境中处理页面布局时,默认的 sizes 占位符值 100vw 是完全合理的。一旦布局样式到位,运行 respImageLint 将为您提供量身定制的 sizes 属性,您可以将其复制并粘贴到您的标记中,其详细程度远高于手写的属性

Responsive image report with suggested dimensions.

虽然服务器渲染标记发起的图片请求发生得太快,以至于 JavaScript 无法生成客户端 sizes 属性,但如果这些请求是客户端发起的,则同样的道理不适用。Lazysizes 项目例如,允许您完全延迟图片请求,直到布局建立之后,从而允许 JavaScript 为我们生成 sizes 值,这为您带来了极大的便利,并保证了为您的用户提供最有效的请求。但是请记住,这种方法确实意味着牺牲服务器渲染标记的可靠性和浏览器中内置的速度优化,并且仅在页面渲染后才启动这些请求会对您的 LCP 分数产生过大的负面影响。

当然,如果您已经依赖于 React 或 Vue 等客户端渲染框架,那么您将已经承担了这种债务,并且在这些情况下,使用 Lazysizes 意味着您的 sizes 属性几乎可以完全抽象出来。更好的是:随着延迟加载图片上的 sizes="auto" 获得共识和原生实现,Lazysizes 将有效地成为这种新标准化的浏览器行为的 polyfill。