适用于任何 Web 框架的完美图像优化

2025-05-24

适用于任何 Web 框架的完美图像优化

这篇文章来自我的“Web Wizardry”简报,我会在其中探索常见 Web 开发问题的常用解决方案(无论您喜欢哪个框架)。如果您喜欢,请免费注册🪄


如果你已经建站一段时间了,“优化图片”可能听起来就像“多吃蔬菜”一样。图片优化确实有利于网站的健康,能让你的 SEO 更上一层楼……但手动压缩每张图片对我来说可不是什么好事🤢

因此,我们将讨论以下轻松获胜的方法:1)使用元素优化图像文件格式和大小picture,以及 2)使用 11ty 的自动化流程,您可以将其带到您选择的构建设置中💪

💁目标读者: 本文面向构建“模板驱动”静态网站(例如 11ty、Jekyll、Hugo、纯 HTML)或“组件驱动” Web 应用(例如 NextJS、Gatsby 等)的开发者。如果您使用 Wordpress 或 Shopify 等网站构建器,那么本文可能不适合您!

🥦 那么我的图像现在有什么问题?

为了说明其中的利害关系,以下是我最近的一篇博客文章中的灯塔评级(请注意,图片是用tinyJPG压缩的!)

Chromium Lighthouse 性能报告中图像加载时间较差的列表

哎呀!抓取这些图片竟然要 10 秒?Chromium 在“较慢”的网速下确实做了一些限制,但显然这些 KB 评分相当高(尤其是对于移动用户而言)。

这只是为了说明图像优化不仅仅是压缩!还有:

  • 提供正确的格式,最好是 JPG,.webp或者.avi 特别如此
  • 提供合适的尺寸,最好是同一张图片的多个副本,宽度和高度不同
  • 在正确的时间加载,尽可能使用“延迟”加载
  • 哎呀,即使包含alt文本也会从可访问性和 SEO 的角度影响您的网站!

我学到了一些使用picture元素解决格式和尺寸问题的方法,我的灯塔肯定因此感谢我😄

picture🌅 修复元素的格式 + 大小问题

那么,我们如何才能为合适的用户提供不同的图片文件呢?好吧,让我们从一个简单的图片元素开始,像这样:

<img src="/assets/mega-chonker.jpg" width="1000" height="600" alt="A perfectly sized cat" />
Enter fullscreen mode Exit fullscreen mode

请参阅此便捷图表来了解“chonk”级别

现在,假设我们打开了图片编辑器,并为移动端用户保存了一个较小的版本,比如说 600 像素宽。你可以设置一些 CSS 来实现根据屏幕宽度热切换图片的功能:

<img class="desktop" src="/assets/chonker-1000w.jpg"
    width="1000" height="600" alt="A perfectly sized cat" />
<img class="mobile" src="/assets/chonker-600w.jpg"
    width="600" height="300" alt="A perfectly sized cat" />
Enter fullscreen mode Exit fullscreen mode
@media(max-width: 600px) {
  .desktop { display: none; }
}
@media(min-width: 601px) {
  .mobile { display: none }
}
Enter fullscreen mode Exit fullscreen mode

……但这不太可扩展。比如说,如果我们处理的是 Markdown 文件,无法添加类名怎么办?或者我们想根据浏览器支持的不同格式(例如 JPEG 和 WEBP)进行切换怎么办?

这就是picture元素发挥作用的地方。请看这个例子:

<picture>
  <!-- List out all the WEBP images + WEBP sizes we can choose from -->
  <source type="image/webp"
    srcset="/assets/chonker-600w.webp 600w, /assets/chonker-1000w.webp 1000w"
    sizes="100vw">
  <!-- In case a browser doesn't support WEBP, fall back to this set of JPG sources -->
  <source type="image/jpeg"
    srcset="/assets/chonker-600w.jpg 600w, /assets/chonker-1000w.jpg 1000w"
    sizes="100vw">
  <!-- The actual, style-able img element that "receives" these sources -->
  <!-- Also includes a default src in case no <source> can be applied -->
  <img src="/assets/chonker-600.png" alt="A perfectly sized cat" />
</picture>
Enter fullscreen mode Exit fullscreen mode

一些重要结论:

  1. 我们可以将图像标签包裹在 中,picture以解锁某种“切换”情况,让浏览器选择source它能够渲染的第一个元素。但不可否认的是,大多数现代浏览器都能处理.webp下面列出的这些文件,type="image/webp"而无需 JPG 格式的后备方案(当前浏览器支持情况请见此处)。
  2. 每个源都有一个srcset属性,该属性接收给定图像格式的源 URL 列表。这些源以逗号分隔,并w在末尾加上一个像素值 width 。然后,浏览器将根据该sizes属性决定使用哪个源(下一节将详细介绍)。
  3. Picture 元素本身并非图像!当你开始尝试为这些图像添加样式时,这是一个有趣的陷阱。因此,你需要将所有与图像相关的 CSS(例如object-fit)放在该img元素上,而不是picture

属性sizes

Sizes是一个有趣的东西。它实际上看起来几乎和 CSS 一样,只是语法上有一些细微的差别。

还记得之前的那些辅助类吗?好吧,mobile我们做一些类似的事情。desktopsizes

视频的要点:

一般来说,该sizes属性是一种告诉浏览器对于给定的屏幕尺寸使用哪个图像的方式。

假设我们有一个横幅图像,它占据了移动用户屏幕的整个宽度,但我们有一个目录,它占据了500px宽屏及以上宽度的一半。

目录占据了屏幕宽度的一半

戴上我们的 CSS 帽子,这意味着我们的图像位于100vw下方(100% 屏幕宽度)500px50vw当我们点击 时@media (min-width: 500px)。这完美地转化为sizes👉sizes="(min-width: 500px) 50vw, 100vw"

在元素的上下文中picture

<picture>
  <!--stack up your media queries as sizes, delineated by commas ","-->
  <source type="image/webp"
    srcset="/img/6dfd7ac6-600.webp 600w, /img/6dfd7ac6-900.webp 900w..."
    sizes="(min-width: 500px) 50vw, 100vw">
  <img alt="Blue and purple cluster of stars" src="/img/6dfd7ac6-600.jpeg">
</picture>
Enter fullscreen mode Exit fullscreen mode

根据经验,100vw对于较小的设备,你应该将其作为“基本情况”,并根据布局的变化情况在顶部添加媒体查询。这意味着sizes根据图像所处的上下文,情况会有所不同,所以如果你使用的是基于组件的框架,请务必注意这一点!

注意:您可能想知道为什么浏览器不能为我们完成所有这些工作。这归结于当您随处使用 CSS 时,“宽度”的不可预测性。如果您像我一样,倾向于使用大量百分比,例如width: 100%用于图像块,这些百分比可能会根据所应用的容器、填充、边距等进行调整。如果浏览器在加载图像之前尝试解析所有这些样式,那么您将等待比预期更长的时间!

尊重高清显示器

请注意,屏幕的像素密度也会影响从给定 中选择哪张图片srcset。对于高密度移动设备显示器,它实际上会选择一个大约是你指定宽度两倍的picture图片!例如,我们有一个简单的声明,如下所示:

<picture>
  <source type="image/webp"
    srcset="/img/galaxy-600.webp 600w, /img/galaxy-1200.webp 1200w"
    sizes="100vw">
</picture>
Enter fullscreen mode Exit fullscreen mode

我们在这里使用100vw,因此浏览器应该将图像源的宽度与显示器的宽度匹配。直观地讲,我们认为600px宽屏显示器会接收/img/galaxy-600.webp……但对于像 MacBook 或现代智能手机这样的高清显示器,它实际上会接收宽度为 600 x 2 像素的图像(/img/galaxy-1200.webp 1200w在本例中)。所以,当你生成多个图像尺寸时,请始终使用较大的值 💡

🔨 使用 11ty image 将其应用到您的网站

好了,我们明白了picture元素的用处……但它的强大程度取决于我们能提供给它的图片。我们真的想手动创建这些尺寸调整精美、优化过、多格式的图片吗?

幸运的是,有很多工具可以帮我们处理这个过程,我将重点介绍我发现的最简单的工具:11ty 的图像插件。

🚨 在你开始滚动到下一部分之前,我先声明一下,你不需要用 11ty 搭建网站才能使用这个工具。试用这个工具后,我发现它非常适合在任何场景下即时生成优化图片,而且不需要任何命令行技能🔥

生成优化图像

回家玩吧!说真的,放下手里的一切,打开你的代码编辑器🧑‍💻然后,创建一个新的目录/文件夹,并创建一个 basic package.json。我们将安装@11ty/eleventy-img依赖项:

mkdir woah-11ty-image-is-cool && cd woah-11ty-image-is-cool
npm init -y # Make a package.json with defaults for everything
npm i @11ty/eleventy-img
Enter fullscreen mode Exit fullscreen mode

现在创建一个随机的 JavaScript 文件供我们玩玩(我称之为 mine )。在里面,只需粘贴11ty 文档image-generator.js顶部的示例代码即可

const Image = require("@11ty/eleventy-img");

(async () => {
  let url = "https://images.unsplash.com/photo-1608178398319-48f814d0750c";
  let stats = await Image(url, {
    widths: [300]
  });

  console.log(stats);
})();
Enter fullscreen mode Exit fullscreen mode

嗯,这看起来很简单。让我们从终端运行它,看看会发生什么:

node ./image-generator.js
Enter fullscreen mode Exit fullscreen mode

如果幸运的话,你会看到几个新面孔出现:

  • 一个包含两张图片的/img目录:一张 300 像素宽的星系 JPG 图片,以及一张大小相同的图片。注意它是如何与代码片段中的数组webp匹配的👀widths
  • 一个包含一些字符串的/cache目录。你可以把它想象成插件的自我提醒,提醒它我们下载了哪些图片。从互联网上下载图片的开销很大,所以为了避免每次运行脚本时都加载图片, 11ty 会检查缓存,看看我们之前是否已经加载过这张图片。

您还将看到控制台中记录了大量“统计信息”。大多数属性都是不言自明的,其中一些属性在我们picture之前的演示中应该很熟悉(即sourceTypesrcset属性)。我们甚至以字节为单位获取图像的输出size,以便您检查格式和大小之间的差异。

等等,还有更多!让我们尝试不同的宽度和格式:

...
let stats = await Image(url, {
  widths: [300, 1000, 1400],
  formats: ['jpg', 'webp', 'gif']
});
...
Enter fullscreen mode Exit fullscreen mode

我们应该在该目录中找到大量的分辨率。正如你所想象的,这对于我们之前的 picture 元素来说非常完美。你可以手动img编写所有的sources 和属性作为学习练习……size

自动化我们的图片元素

……或者让插件帮我们搞定!有了方便的数组stats,11ty image 会把所有内容拼接成一个有效<picture>元素。我们只需要调用一下generateHTML辅助函数:

const Image = require("@11ty/eleventy-img");

(async () => {
  let url = "https://images.unsplash.com/photo-1608178398319-48f814d0750c";
  let stats = await Image(url, {
    widths: [300, 1000, 1400]
  });
  const html = Image.generateHTML(stats, {
    alt: "A blue and purple galaxy of stars", // alt text is required!
    sizes: "100vw" // remember our training with "sizes" from earlier...
  })

  console.log(html);
})();
Enter fullscreen mode Exit fullscreen mode

如果幸运的话,我们应该会看到一个picture可以在我们网站的任何地方使用的美丽的东西:

<picture>
    <source type="image/webp"
          srcset="/img/6dfd7ac6-300.webp 300w, /img/6dfd7ac6-1000.webp 1000w,
                  /img/6dfd7ac6-1400.webp 1400w"
          sizes="100vw">
    <source type="image/jpeg"
          srcset="/img/6dfd7ac6-300.jpeg 300w, /img/6dfd7ac6-1000.jpeg 1000w,
                  /img/6dfd7ac6-1400.jpeg 1400w"
          sizes="100vw">
    <img alt="A blue and purple galaxy of stars" src="/img/6dfd7ac6-300.jpeg" width="1400" height="1402">
</picture>
Enter fullscreen mode Exit fullscreen mode

更进一步

这个插件还有很多额外的选项可以探索,比如

📣 在任何框架中使用 11ty 镜像

如果所有这些<picture>疯狂的想法让你兴奋不已,那就把这个 11ty 图片插件放到你自己的/assets目录中吧!我写了个方便的小脚本,用来抓取目录中的所有图片(注意,不是递归的),并输出一些优化过的文件:

const Image = require('@11ty/eleventy-img')
const { readdir } = require('fs/promises') // node helper for reading folders
const { parse } = require('path') // node helper for grabbing file names

;(async () => {
  const imageDir = './images' // match this to your assets directory
  const files = await readdir(imageDir)
  for (const file of files) {
    const stats = await Image(imageDir + '/' + file, {
      widths: [600, 1000, 1400], // edit to your heart's content
      filenameFormat: (id, src, width, format) => {
        // make the filename something we can recognize.
        // In this case, it's just:
        // [original file name] - [image width] . [file format]
        return `${parse(file).name}-${width}.${format}`
      },
    })
    console.log(stats) // remove this if you don't want the logs
  }
})()
Enter fullscreen mode Exit fullscreen mode

如果您恰好在个人网站上使用 11ty(或者至少想尝试一下),您picture也可以自动插入元素。他们的指南picture涵盖了如何构建您自己的“短代码”函数,以便为网站上每张未优化的图片插入正确的代码。

即使没有这种额外的功能,这个脚本对于任何基于 JS 的构建步骤来说都是一个很好的补充。下面是一个基本Image组件,我可以根据上面的脚本将其添加到任何 React 应用中:

// consider using TypeScript for checking all these props!
const Image = ({ fileName, sizes, ...imageProps }) => (
    <picture>
      <source
        type="image/webp"
        srcSet={`/img/${fileName}-600.webp 600w, /img/${fileName}-1000.webp 1000w, /img/${fileName}-1400.webp 1400w`}
        sizes={sizes}
      />
      <source
        type="image/jpeg"
        srcSet={`/img/${fileName}-600.jpeg 600w, /img/${fileName}-1000.jpeg 1000w, /img/${fileName}-1400.jpeg 1400w`}
        sizes={sizes}
      />
      <img src={`/img/${fileName}-600.jpeg`} {...imageProps} />
    </picture>
)
Enter fullscreen mode Exit fullscreen mode

假设我的所有图像都按照这个文件命名约定生成(并且我的图像宽度始终为 600、1000 和 1400),这应该可以毫无问题地提取我们所有优化的图像👍

以下是将这些知识应用于以下方面的简要概述create-react-app

亲自尝试一下

您可以在此 CodeSandbox 🪄中查看create-react-app+11ty 图像的运行示例

此版本还将在开发过程中监听新图像。欢迎您fork 源代码并在您自己的项目中尝试(并发现我不可避免地遗漏的一些极端情况 😉)。

Next、Nuxt、Gatsby 等的其他选项

尽管 11ty 图像很酷,但我还是应该强调一些流行元框架的“原生”选项:

  • 对于 Next 来说,其内置的图像组件非常完美。它还能自动覆盖我们的尺寸、格式和图像压缩,此外还有一些简洁的道具,可以使用 来快速加载“首屏”图像priority
  • 对于 Nuxt,他们的<nuxt-img><nuxt-picture>组件应该可以满足您的需求。它们提供与我们的 11ty 图片插件大部分相同的功能,允许您指定不同的格式、属性以及背景图片压缩。如果您想允许多种图片格式而不是仅一种,sizes请务必使用!nuxt-picture
  • 对于 Gatsby 来说,它拥有图像优化的黄金标准🏆他们的图像插件实际上是我几年前使用该框架的主要原因,而且它现在也越来越好了。最棒的功能(除了我们之前讨论的所有内容之外)是它们的图像加载动画。你可以淡入淡出图像的矢量轨迹,使用模糊效果等等。唯一的缺点是它需要加载到浏览器中大量的 JS 包来实现这一点,我在这里分享了我的一些看法
  • 除了框架之外,您还可以使用 Cloudinary 之类的工具进行远程优化如果您不负责网站的构建过程,或者不想将图片存储在代码仓库中,那么 Cloudinary 是一个不错的选择。例如,您可以将所有 Wordpress 图片指向 Cloudinary 的 bucket,并从那里提取不同宽度和格式的图片。唯一的缺点是成本,因为 Cloudinary 会为您完成所有这些图片处理和存储工作。

学到一点东西吗?

很高兴听到这个消息!如果你想了解更多类似的通用 Web 开发解决方案,可以订阅 Web Wizardry 的新闻简报,每两周更新一些知识点。

文章来源:https://dev.to/bholmesdev/picture-perfect-image-optimization-for-any-web-framework-2o77
PREV
为什么 SvelteJS 可能是新 Web 开发者的最佳框架
NEXT
如何轻松恢复你的 git 提交