Image optimisation

2025-05-28

图像优化

最近我有机会在NDC 悉尼发表关于网络性能的演讲,并获得了热烈的反响。

这启发了我针对那次演讲中涉及的每个主题撰写一系列帖子,谁知道呢,也许有一天这些帖子都会成为他们自己的演讲😃。

所有其他部分:

第 1 部分:HTML 和 CSS

第 2 部分使用 Preload/Prefetch 来提升加载时间

第 3 部分 JavaScript 技巧和窍门

第五部分 Web 字体优化

简介

相信我,你肯定不想让谷歌讨厌你的网站。要知道,
每个网站上最重的元素就是图片,因此,专注于图片的优化至关重要。

幸运的是,减小它们的尺寸非常容易,而且对页面的整体大小影响巨大。对于大多数移动设备用户来说,图像质量并不那么重要。即使在高分辨率的桌面设备上,也需要做出一些牺牲。这是因为人眼无法检测到某些缺失的像素。

只要您不要过度优化图像而使其变得难看,继续减小其尺寸是件好事。

图片占网页总重量的50%以上😱。这就是为什么我们应该优化它们!

每页平均字节数
2018 年每页平均字节数

图像优化是什么意思

图像优化是指使用不同的技术来减小图像尺寸。这将使页面加载速度更快,从而带来更好的用户体验。

何必呢

优化图像可带来以下好处:

  • 它将缩短页面加载时间。用户每等待一秒页面加载,亚马逊每年就会损失 16 亿美元的销售额(现在你可以用计算器看看你的公司会受到多大的影响😁)。
  • 它可以提高您的 SEO 能力,您的网站在搜索引擎中的排名会更高,从而带来更多的流量。
  • 创建站点备份会更快(如果您使用 CMS 或备份整个站点)
  • 较小的图像尺寸占用较少的带宽,从而不会耗尽用户的移动数据配额。
  • 需要服务器(或 CDN)上的存储空间更少,从而更具成本效益。

让我们优化它们

图片优化的主要目标是在文件大小和图片质量之间找到平衡。但在开始讨论优化技巧之前,我们应该先了解不同的图片格式以及各自的使用时机。

选择正确的格式

  • PNG – 图像质量更高,但文件大小也更大。它最初是作为无损图像格式创建的,但也存在有损的情况。
  • JPEG – 使用有损和无损优化。您可以调整质量级别,以在质量和文件大小之间取得良好的平衡。
  • GIF – 仅使用 256 种颜色。它是动画图像的最佳选择。它仅使用无损压缩。

有一些较新的图像格式,例如WebPJpeg2000,但浏览器尚未支持。总而言之,对于色彩丰富的图像,应使用 JPEG 格式;对于较为简单的图像,应使用 PNG 格式。

[更新]
对 WebP 和 Jpeg2000 的支持已大幅改进,请放心使用。不过,请确保您有备用方案,以防万一它们不再受支持,毕竟您肯定不想让用户无法查看这些图片。

<picture>
  <source srcset="img/awesomeWebPImage.webp" type="image/webp">
  <source srcset="img/creakyOldJPEG.jpg" type="image/jpeg"> 
  <img src="img/creakyOldJPEG.jpg" alt="Alt Text!">
</picture>
Enter fullscreen mode Exit fullscreen mode

上述代码片段是支持所有浏览器(包括新旧浏览器,甚至不支持图片的浏览器)的最佳组合。

尺寸与压缩

这是压缩前后图像的示例。请注意图像质量是如何受到影响的(虽然在猫身上看不到,但在它周围可以看到):


猫


猫

出于同样的原因,你无法分辨出第一张图片和第二张图片中猫咪本身的区别,所以将图片压缩到这个程度是安全的。需要注意的是,第一张图片有 4mb,而第二张图片只有 27kb。🤷‍

有损优化与无损优化

现在您知道压缩图像和降低质量的重要性,了解两种压缩类型也很重要:

  • 有损- 这是一种滤镜,可以消除图像中的部分数据。在上面的例子中,您可以看到猫周围的区域有一些损失。使用此技术可以大幅减小文件大小。Adobe Photoshop、Affinity Photo 或一些免费的在线工具(例如Image Compressor)可以帮您实现这一目标。

  • 无损- 这是一种不会删除任何数据的滤镜,仅使用压缩来减小文件大小,但这意味着需要先解压图像才能运行。您可以使用FileOptimizerImageOptim等工具轻松实现这一点。

您需要亲自体验,找到最适合您图像的最佳效果。这项任务需要事先做一些工作,但可以为您节省大量时间。另外,在构建过程中,您可以考虑使用 ImageOptim 等工具,这样您甚至无需担心前期工作。而且您的原始图像将保持原样。

使用正确的尺寸

压缩虽然重要,但其本身只能在一定程度上保持尺寸不变。应用压缩后,您将无法再在宽度和高度不变的情况下缩小尺寸。

除此之外,你需要知道,在移动设备上显示 2000px 宽度的图片并不是一个好主意。尤其是在较小的设备上,人眼察觉变化的能力远不如在高宽比的显示器上。

为此,您可以使用中的srcsetandwidth descriptors属性HTML。这样,您可以提及多种屏幕尺寸,并指定每种尺寸要使用的图像。

使用 srcset 的猫图片

当您使用宽度描述符时,您将向浏览器提供图像列表及其真实宽度,以便它可以根据视口大小选择最佳源。

使用 SVG

SVG 是一种可缩放的矢量格式,非常适合用于徽标、图标、文本和简单图像。以下是您可以考虑使用它们的几个原因:

  • SVG 在浏览器和图片编辑工具中均可自动缩放。这简直是网页设计师和平面设计师的梦想!
  • Google 对 SVG 进行索引的方式与对 PNG 和 JPG 进行索引的方式相同,因此您不必担心 SEO。
  • SVG 文件大小通常(但并非总是)比 PNG 或 JPG 更小。这可以缩短加载时间。

这里有一个例子来告诉你它可以有多大的差异(图片来自https://genkihagata.com):

JPEG
JPG
大小:81.4KB
巴布亚新几内亚
巴布亚新几内亚
大小:85.1KB
SVG
JPG
大小:6.1KB

注意:我无法在这篇文章中嵌入 SVG 文件,所以我只使用了 jpeg 文件。不过你可以在我的原帖中查看。

延迟加载图像

回顾我们迄今为止所经历的一切,你会意识到,仅仅缩小图片尺寸已经不够了,尤其是在页面上图片过多的情况下。这时,我们需要确保网页能够快速加载图片。

这时,延迟加载就派上用场了。我们来看一个演示,看看它是如何工作的(视频来自CSS Tricks):

它是什么?

图像延迟加载简单来说就是在稍后的时间点才加载图像。它是 Web 开发中的一项技术,适用于许多其他形式的资源,但这里我们主要关注图像。

维基百科:延迟加载是计算机编程中常用的一种设计模式,用于将对象的初始化推迟到需要时才进行。如果使用得当,它可以提高程序的运行效率。

如何完成

假设你有一个很长的页面,里面有很多图片。如果用户看不到页面底部的图片,那为什么还要加载呢?很简单,你可以在某个事件(例如页面底部可见时,使用滚动事件处理程序)或其他事件中加载图片。但不仅仅是在页面加载时加载。

除此之外,如果用户从不向下滚动,则该图像将不会被加载,从而为最终用户节省一些网络流量和数据使用量。

考虑到这对整体页面加载时间和速度的影响有多大,您将开始看到很多好处。

延迟加载技术

在页面上加载图像有两种常见的方法:使用img标签和 CSS background-image。让我们从图像标签开始。

图像标签

下面是我们通常用来加载图像的简单图像标签:

<img src="/path/to/some/cat/image.jpg" />
Enter fullscreen mode Exit fullscreen mode

延迟加载图片的标记非常相似。该src属性触发浏览器发送网络请求并获取图片。无论这是页面上的第一张图片还是第 50 张图片。

要延迟加载,只需使用data-src属性。

<img data-src="/path/to/some/cat/image.jpg" />
Enter fullscreen mode Exit fullscreen mode

由于为src空,浏览器在渲染标签时不会加载图片。现在只需触发加载即可,通常在图片进入视口时完成。

我们可以使用诸如scrollresize和 之类的事件orientationChange来确定何时触发加载。滚动事件非常明确,当用户滚动页面时,如果图像标签位于页面上,我们就会触发加载并告诉浏览器获取图像。然而,调整大小和方向改变事件同样重要。调整大小是指用户改变窗口大小(例如缩小窗口)。方向改变是指用户旋转设备时。

一旦我们钩住这些事件,我们就可以启用延迟加载,结果非常好:

document.addEventListener(
  'DOMContentLoaded',
  function() {
    var lazyloadImages = document.querySelectorAll(
      'img.lazy'
    )
    var lazyloadThrottleTimeout

    function lazyload() {
      if (lazyloadThrottleTimeout) {
        clearTimeout(lazyloadThrottleTimeout)
      }

      lazyloadThrottleTimeout = setTimeout(
        function() {
          var scrollTop = window.pageYOffset
          lazyloadImages.forEach(function(img) {
            if (
              img.offsetTop <
              window.innerHeight + scrollTop
            ) {
              img.src = img.dataset.src
              img.classList.remove('lazy')
            }
          })
          if (lazyloadImages.length == 0) {
            document.removeEventListener(
              'scroll',
              lazyload
            )
            window.removeEventListener(
              'resize',
              lazyload
            )
            window.removeEventListener(
              'orientationChange',
              lazyload
            )
          }
        },
        20
      )
    }

    document.addEventListener('scroll', lazyload)
    window.addEventListener('resize', lazyload)
    window.addEventListener(
      'orientationChange',
      lazyload
    )
  }
)
Enter fullscreen mode Exit fullscreen mode

使用交叉口 API

让我们看看这个 API 提供了什么:

交叉观察器 API 提供了一种异步观察目标元素与祖先元素或顶级文档视口的交叉点变化的方法。

与之前的技术相反,您可能会看到由于所有这些事件处理程序而对页面性能产生一些影响,而这种方法相对较新。

此 API 通过进行数学运算并提供一种非常有效的方法来在资源出现在屏幕上时调用回调函数,从而消除了之前的性能损失:

document.addEventListener(
  'DOMContentLoaded',
  function() {
    var lazyloadImages

    if ('IntersectionObserver' in window) {
      lazyloadImages = document.querySelectorAll(
        '.lazy'
      )
      var imageObserver = new IntersectionObserver(
        function(entries, observer) {
          entries.forEach(function(entry) {
            if (entry.isIntersecting) {
              var image = entry.target
              image.src = image.dataset.src
              image.classList.remove('lazy')
              imageObserver.unobserve(image)
            }
          })
        }
      )

      lazyloadImages.forEach(function(image) {
        imageObserver.observe(image)
      })
    } else {
      var lazyloadThrottleTimeout
      lazyloadImages = document.querySelectorAll(
        '.lazy'
      )

      function lazyload() {
        if (lazyloadThrottleTimeout) {
          clearTimeout(lazyloadThrottleTimeout)
        }

        lazyloadThrottleTimeout = setTimeout(
          function() {
            var scrollTop = window.pageYOffset
            lazyloadImages.forEach(function(img) {
              if (
                img.offsetTop <
                window.innerHeight + scrollTop
              ) {
                img.src = img.dataset.src
                img.classList.remove('lazy')
              }
            })
            if (lazyloadImages.length == 0) {
              document.removeEventListener(
                'scroll',
                lazyload
              )
              window.removeEventListener(
                'resize',
                lazyload
              )
              window.removeEventListener(
                'orientationChange',
                lazyload
              )
            }
          },
          20
        )
      }

      document.addEventListener(
        'scroll',
        lazyload
      )
      window.addEventListener('resize', lazyload)
      window.addEventListener(
        'orientationChange',
        lazyload
      )
    }
  }
)
Enter fullscreen mode Exit fullscreen mode

我们将观察者附加到所有想要延迟加载的图片上。当 API 检测到元素已进入视口时,isIntersecting我们会使用该属性从属性中获取 URL,data-src并将其移动到src属性中,以便浏览器像以前一样触发图片加载。完成后,我们从图片中移除 lazy 类,并从图片中移除观察者。

CSS 背景图像

CSS 背景图像不像 image 标签那样简单。要加载它们,浏览器需要构建 DOM 树和 CSSDOM 树(参见此处)。如果 CSS 规则适用于该节点,浏览器就会加载它,否则则不会加载。因此,我们需要做的就是默认不为其添加 background 属性,并在其可见时添加:

document.addEventListener(
  'DOMContentLoaded',
  function() {
    var lazyloadImages

    if ('IntersectionObserver' in window) {
      lazyloadImages = document.querySelectorAll(
        '.lazy'
      )
      var imageObserver = new IntersectionObserver(
        function(entries, observer) {
          entries.forEach(function(entry) {
            if (entry.isIntersecting) {
              var image = entry.target
              image.classList.remove('lazy')
              imageObserver.unobserve(image)
            }
          })
        }
      )

      lazyloadImages.forEach(function(image) {
        imageObserver.observe(image)
      })
    } else {
      var lazyloadThrottleTimeout
      lazyloadImages = document.querySelectorAll(
        '.lazy'
      )

      function lazyload() {
        if (lazyloadThrottleTimeout) {
          clearTimeout(lazyloadThrottleTimeout)
        }

        lazyloadThrottleTimeout = setTimeout(
          function() {
            var scrollTop = window.pageYOffset
            lazyloadImages.forEach(function(img) {
              if (
                img.offsetTop <
                window.innerHeight + scrollTop
              ) {
                img.src = img.dataset.src
                img.classList.remove('lazy')
              }
            })
            if (lazyloadImages.length == 0) {
              document.removeEventListener(
                'scroll',
                lazyload
              )
              window.removeEventListener(
                'resize',
                lazyload
              )
              window.removeEventListener(
                'orientationChange',
                lazyload
              )
            }
          },
          20
        )
      }

      document.addEventListener(
        'scroll',
        lazyload
      )
      window.addEventListener('resize', lazyload)
      window.addEventListener(
        'orientationChange',
        lazyload
      )
    }
  }
)
Enter fullscreen mode Exit fullscreen mode

和:

#bg-image.lazy {
  background-image: none;
  background-color: #f1f1fa;
}
#bg-image {
  background-image: url('path/to/some/cat/image.jpg');
  max-width: 600px;
  height: 400px;
}
Enter fullscreen mode Exit fullscreen mode

概括

我们已经了解了如何使用不同的压缩方法减小图片大小,如何根据不同的屏幕尺寸加载不同的图片尺寸,以及如何实现延迟加载。使用这些技巧,您可以大幅提升页面性能,甚至在一段时间后,您也会将其视为一种爱好。

和往常一样,请传播这个消息并期待下一篇关于网络字体的文章😃👋。

文章来源:https://dev.to/yashints/image-optimization-3ic9
PREV
提高 HTML 和 CSS 性能
NEXT
我如何跟上前端世界的背景 1. 阅读 2. 关注酷人 3. 会议和聚会 4. 视频、在线课程和播客 5. 宠物项目 6. 写作 自律 永不放弃,学会放松 总结