Day06 X 图片最佳化

给你五秒钟思考一下,你在日常生活中还有在使用没有任何图片,包括小小 的 Icon 也没有的网站吗?我想大多数人的答案都是否定的。现今的网页应用免不了会需要载入大量的图片来加强网页的视觉与提供方便使用者吸收的资讯,图片因此成为网站中占比最多的资源,让我们换位思考,也就是说如果能优化这些图片资源,也许会让网站效能带来很大的提升。


https://httparchive.org/ 这个网站定期针对前一百万流量最多的网站进行 benchmark,发现图片往往是平均起来占比最大的资源。

Images are heavy.

图片有不同格式,你得先了解它们适合的使用时机

不同的图档类型(如 .png .svg .jpg)有各自适合使用的时机,学会将不同类型图片应用在适合的地方不仅可以提升使用者体验或 UI 品质,某些状况下也可以控制载入资源的大小进而提升效能。

先思考一下真的需要使用图片吗?

如果你的视觉需求用简单的 CSS 就可以达成,就千万不要多此一举去使用图片罗!

JPEG & PNG

我们知道 JPG 与 PNG 是由像素阵列(Pixel Array)来表示的图像,又被称为「点阵图」,这两种格式我想也是目前网页中最常被使用的两种格式。不过它们的档案大小比较大,代表载入需要消耗比较多的时间与浏览器效能,所以我们得了解它们的使用时机,以及什麽时候可以寻找替换的格式。

在这之前我们得先聊聊图片压缩,透过图片压缩,可以大幅减少图片的大小,以此来加快网站的载入速度。

可以看到虽然每张图片的压缩效果有差异,但至少都可以减少一定大小,这对於网站效能来说有相当大的帮助,因此现在开发网站时图片基本上都一定会经过压缩,有的企业会选择建立自己的图片压缩服务,或是使用 webpack 等 module bundler 来将压缩整合到开发流程里,不过我们也可以使用一些第三方的压缩服务,例如:

建议读者可以自己玩玩看喔!

压缩图片是银弹吗?

虽说压缩图片真的可以显着降低图片的大小,但真的没有任何 trade off 吗?其实压缩又分为两种情况:

  • 有损压缩:如 JPG,使用只取部分像素资料的方式来压缩图片大小,并且压缩後是不可逆的。
  • 无损压缩:如 PNG,压缩後不影响图片品质。

所以是否在意图片品质是压缩前要思考的事喔!(不过其实单纯用肉眼看,画质看起来并不会差太多)

回过头来讨论不同图片格式适合使用的时机,通常相同尺寸且都经过压缩後的 png 图档大小会比 jpg 还要大,原因在於 jpg 是采用失真压缩演算法,因此压缩率比 png 还要大。所以以我自己过往的经验来说,如果是非常大的背景图片,例如以下网站的背景图

我会选择使用 jpg,不仅档案小了许多,实际上的视觉品质也不会差异太大。这样看起来 png 是不是挺没用的?当然不是。png 在处理含有文字、线条和轮廓较为明显的影像表现较 jpg 来的好,当影像有明显轮廓边缘,jpg 的失真演算法在处理这种边缘上就会产生瑕疵,png 的品质则会因为采用无失真演算法而较好。

另外如果你的图档未来是会一直有更改需求的,建议使用 png,原因是 jpg 在每次修改储存後可能会导致图片品质越来越差。

SVG

SVG 就是我们熟知的向量图

SVG 的优点有

  • 无论放大多少都不会变模糊
  • 体积也比点阵图还要小。
  • SVG 可以透过 Gzip 压缩,PNG 与 JPG 透过 Gzip 压缩档案大小可能不会变小,甚至还可能更大。原因在於 PNG 与 JPG 本身就已经是经过压缩的格式了。

如果是适合的使用情境,会建议能够使用 SVG 就优先使用,SVG 适合的情境如企业的 Icon、图表、图示...等。而 PNG 与 JPG 等点阵图其实也可以转为向量图,但并不是所有的点阵图都适合转档,比较适合的情境如

  • 主要由几何图形组成的图片
  • 较小的图片 (长x宽)
  • 不希望产生图片失真、边缘会有点模糊的状况

与 PNG 与 JPG 一样,SVG 图档也可以进行最小化优化,例如可以透过 SVG 优化工具如 svgomg 来降低图档大小。

其实 SVG 的使用方式还蛮多元的,真的要说的话可能得分个几篇文章来讲解,不过继既然系列文的主题是效能优化,就不多花篇幅在这上面了,有兴趣的读者可以参考以下几个关键字:

  • SVG with CSS
  • Inline SVG
  • SVG With Base64

WebP

WebP 是 Google 於 2010 年推出的图像格式,最大的优势就是它的档案大小比 PNG 与 JPG 小很多,

让我们直接看看对比,YouTube 提供了 thumbnail image 的 URL,只要带进影片 id 就能拿到影片的缩图照片,也可以透过参数获得不同格式与解析度的图片。

https://i.ytimg.com/vi_webp/ZPBWje2kJ_U/maxresdefault.webp
https://i.ytimg.com/vi/ZPBWje2kJ_U/maxresdefault.jpg

在相同解析度的情况下,WebP 格式的大小为 195 KB,jpg 则为 285 KB,当然,这些都是已经经过压缩的图片,可以发现 WebP 真的是小了不少。

WebP 另外一个优势就是支援动图,也就是说常见的 GIF 也可以转为 WebP 并且获得更小的体积。

所以说 WebP 基本上是现代网页的第一选择,浏览器支援度的部分也只剩下 IE 没有支援与 Safari 只有部分支援,而 IE 将在 2022 年终止服务了,基本上不用理会它(我好坏),所以可以说大多数主流浏览器都已经支援 WebP 罗!

最後附上 PNG、JPG、WebP 三种图片格式的比较图

Responsive Image

近年来装置的数量与种类变得十分多元,手机、平板、笔电...等,会随着厂牌与产品种类有不同的萤幕大小,如果开发者只提供一张照片,就想在不同的装置都能够完美显示,是有点困难的。首先图片可能会因此长宽比例失衡(除非把宽高写死了)或是解析度失真,再来如果在小尺寸的装置下载非常大尺寸的照片,不仅增加 page load time,浏览器也会为了缩小大图而耗费性能。

这也是 LightHouse 衡量 performance 的一个重要 metric,详细内容可以参考这里

所以说,比较好的方法应该是针对不同的浏览器视窗大小、装置解析度,呈现对应大小的图片,这就是 Responsive Image 响应式图片。

行前知识点:

  • Viewport: 视窗大小,指网页的可视区域。
  • DPR (Device Pixel Ratio): 萤幕像素与 CSS pixel 之间转换的倍率值(这个概念比较抽象,可以参考这篇文章)。

在说明 Responsive Image 的用法之前,先分享一个自己曾经的疑惑。

为什麽设计师出给我的图,同一张会有分 1x, 2x, 3x 三个版本啊?不能只用其中一个就好吗?

这里的 1x, 2x, 3x 代表的其实是图片的解析度(其实也跟图片本身的大小有关),假设大多数装置的 DPR 为 1,不过像是 iphone 的某些型号 DPR 可能会是 2 或是 3,这时如果呈现的还是适合 DPR 1 的图片的话,就很有可能会产生模糊的状况,因此才需要出不同尺寸的图,依照装置的 DPR 来呈现尺寸适合的图片。(在这里诚挚地跟之前跟我合作的设计师道歉,年少不懂事,出了 1x, 2x, 3x 给我,结果我没搞清楚原因就只用了 3x ?)

Responsive Image Level 1

大家应该都知道在 HTML 中,图片的 element 基本用法是

<img src="ironman.jpg" />

其实我们还可以透过 srcset 这个属性,给予面对不同 DPR 时要显示的图片

<img 
  src="ironman.jpg"
  srcset="ironman_1x.jpg 1x, ironman_2x.jpg 2x" />

在 DPR 为 1 的情况下,浏览器会显示 ironman_1x.jpg 这张图,而在 DPR 为 2 的情况下,浏览器则会显示 ironman_2x.jpg 这张图,原来的 src 则变为 fallback 的选项,如果当前浏览器不支援 srcset 属性或是找不到对应的 DPR,就会 fallback 成 src 的图片。

Responsive Image Level 2

也许今天设计师出给你的图并不是完全遵照 DPR 比率,不能用 1x, 2x 3x 来区别,只给了你 small, mid, large 三种版本的图片,在这种状况下,我们会希望可以由浏览器自己决定当前状况下应该要显示哪一张图片,Img Element 也能够做到这件事

<img 
  src="ironman_small.jpg"
  srcset="ironman_small.jpg 300w, ironman_mid.jpg 600w, ironman_large.jpg 1000w"
/>

眼尖的读者应该发现图片後的 1x, 2x 被换成 w 了,使用 w 後浏览器会根据装置的 DPR 与 Viewport 来决定图片的宽度。

例如说在 DPR 为 1 且萤幕宽度为 450px 的装置下,浏览器选择的图片尺寸会是 1*450px = 450px,因为超过 ironman_small 的 300,却在 ironman_mid 的 600 之内,浏览器会选择使用 ironman_mid 这张图片。如果是在 DPR 为 3 且萤幕宽度一样为 450px 的装置, 3*450 = 1350px,浏览器则会选择 large 版本的图片。

Responsive Image Level 3

如果使用了 step 2 提到的 w 的方式,就必须明确告诉浏览器图片的大小。原因是平常我们可以透过 css 来指定 img element 的大小,但是浏览器会先解析 HTML 并开始载入图片,经过这些步骤才会开始读取 CSS 。前面提过,响应式图片想让浏览器自行决定要下载哪个尺寸的照片,但这个时候因为还没读取 CSS,所以浏览器根本不知道图片的尺寸,那该怎麽办呢?不如我们自己告诉它吧。

<!-- 新增了 sizes attribute -->

<img 
  src="ironman_small.jpg"
  sizes="(max-width: 400px) 80vw, 50vw"
  srcset="ironman_small.jpg 300w, ironman_mid.jpg 600w, ironman_large.jpg 1000w"
/>

这边可能会有读者误会 size 属性是去改变图片的大小(当初的我也踩坑了),但是它其实只是告知浏览器选择图片尺寸的条件,如果都没有指定的话,预设会是 100vw。

上面这段 HTML 的意思是:如果 viewport 小於或等於 400px,那图片的宽度为 80vw (viewport 的 80%),其余的状况则为 50vw (viewport 的 50%)。假设在 DPR 为 1,萤幕宽度为 450px 的装置下,所需的图片尺寸为

450px * 0.5 (因为大於 400px,因此图片宽度为 50vw)* 1 = 225px

因此浏览器会选择下载 ironman_small.jpg 这张图片。

Art direction

首先来看看 MDN Doc 对於 Art direction 给出的解释与范例

The art direction problem involves wanting to change the image displayed to suit different image display sizes.

也就是希望能够根据 viewport 指定不同尺寸的图片,不然同一张图从电脑版换到手机版如果等比例缩放的话应该会变很丑,或是图片中的人事物会变得很小,这个问题我们可以利用 <picture> 这个 HTML element 来解决。

<picture> 是一个里面可以装有多个 <source> element 的 wrapper,浏览器会根据条件「由上而下」选出适合当前情况的 source。

<picture>
  <source
    media="(min-width: 800px)"
    srcset="ironman-large-1x.jpg 1x, ironman-large-2x.jpg 2x"
  />
  <source
    media="(min-width: 500px)"
    srcset="ironman-medium-1.jpg 1x, ironman-medium-2.jpg 2x"
  />
  <img src="ironman-small.jpg" alt="ironman" />
</picture>

在 viewport 大於 800px 的情况下,浏览器会根据 DPR 是 1 或是 2 选择 ironman-large-1x.jpg 或是 ironman-large-2x.jpg,如果 viewport 介於 800px 与 500px 之间,则会根据 DPR 从 ironman-medium-1.jpg 与 ironman-medium-2.jpg 之间挑选,最後的 img element 与前面提到的一样作为条件都没有 match 时的 fallback option。

还能处理浏览器对图片格式支援度问题

前面有说过 WebP 因为档案大小的优势已经成为图片的首选格式,不过有些浏览器仍不支援,难道我得写 JavaScript 判断浏览器有没有支援 WebP 再看要不要替换掉图片吗? 不用,picture 也强大到可以处理这件事了。

<picture>
  <source srcset="ironman.webp" type="image/webp" />
  <source srcset="ironman.jpg" type="image/jpg" />
  <img src="ironman.jpg" alt="ironman" />
</picture>

刚刚有说过 picture 中的 source 是「由上往下」判断的,所以我们将最想要的 WebP 格式放在最上面,如果浏览器没有支援 WebP,就会往下一个 source 寻找图片,是不是很方便呢?

我们也可以将上面 media query 与 WebP 的范例结合在一起

<picture>
  <source 
      media="(min-width: 1920px)"
      srcset="ironman.webp" 
      type="image/webp" />
  <source 
      media="(min-width: 1920px)"
      srcset="ironman.jpeg" 
      type="image/jpeg" />
  <source
      media="(max-width: 500px)"
      srcset="ironman_small.webp" 
      type="image/webp" />
  <source
      media="(max-width: 500px)"
      srcset="ironman_small.jpg" 
      type="image/jpg" />
  <img src="ironman.jpg" alt="ironman" />
</picture>

我自己觉得 picture + source 真的非常强大,可以搭配许多 attribute 处理不同状况,记得亲自去体会看看喔!

<img srcset size>、<picture> 与 CSS Media Query 的差别

今天介绍的 <img srcset size>、<picture> 将图片的选择权交给了浏览器,让浏览器在解析 HTML 时依当时状况决定要下载什麽样的图片,而使用 Media Query 则是得先将全部图档先下载下来,再依照在 Media Query 里设定的条件决定要显示哪张图片,因为必须先将图片都下载下来,势必会发出较多的网路请求,造成效能的耗损。

Image CDN

看到现在读者应该会发现面对多元装置的状况,一个应用可能会需要出非常多尺寸的图,但并不是所有人在开发时都有专业的设计时帮你做好不同尺寸的图档,这时可以考虑使用第三方的 Image CDN 服务 (关於 CDN 会在日後的章节详细介绍),Image CDN 可以帮你即时转换、resize 与优化图片,并且存在 CDN 快取中,加速图片传递的速度。

可以参考一些比较有名的 Image CDN 服务:

使用的方式大多是透过它们提供的 URL API,并带入想要的图片格式与图片尺寸等条件来拿到图档,例如

https://res.cloudinary.com/fec-demo/image/fetch/w_200,h_200/{image path}.jpg'

有兴趣的读者可以参考这个教学影片

图片优化 With Module Bundler

前面有提到我们可以利用 module bundler 来将图片优化整合进开发流程里,这边就来简单看看范例吧!因为笔者比较熟悉 Webpack,所以将会以 Webpack 为示范,不过相信其他 bundler 应该也有类似的工具,就交给各位自行研究罗。

Webpack 的许多功能都可以依靠 plugins 来达成,在 Webpack 中,可以透过 file-loader 或是 url-loader 来达成图片打包,不过两者相比,url-loader 多了 limit 的 config,只要图片大小在 limit 之内,就会将资源转成 DataURI (base64) 的格式并放到引入图片的档案里。转成 base64 的好处在於,网页在渲染图片时,不需要再透过额外的 request 抓取档案,直接到档案拿就好了,也就是说可以透过减少 Round Trip Time 来增加效能。不过这边会限制 limit 当然就意味着不能都不管图片大小就卯起来转成 base64,原因是 base64 图片内容会被 inline 到档案中,最後打包时加在 JavaScript budle 中,如果不作限制,一不小心就可能造成 JavaScript 的 bundle size 过於肥大,反而拖垮了载入效能,因此必须好好衡量 limit 的上限喔!

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|gif|jpg)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8787, // 限制须转为 base64 的文件大小 (单位:byte)
            },
          },
        ],
      },
    ],
  },
};

Base64 的优缺点

Base64 将图片转为编码字串,让开发者能将图档嵌入 HTML、CSS 或 JavaScript 程序码,使用图片时不需要再透过额外网路请求,以减少 Request。适合用在较少更新的小图,例如 icon 等。使用方式为:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAYAAAA9zQYyAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzA..." />

要将图片转换成 base64 格式可以透过一些的 tool 例如 Base64 Image Encoder 来将图片作转换

Base64 的优点:

  • 也就是前面提过的可以减少 HTTP Request。

Base64 的缺点:

  • 图档平均来说比原本二进位档案还大,这也是为什麽比较适合用在小图。
  • 维护上比较困难。图档若有改动,整个编码会不一样,没办法像改动档案或连结一样容易,通常会搭配自动化工具与 plugins 如 postcss-base64 来维护。
  • 因为不是图档,所以没办法被浏览器快取。
  • 档案会变大。Base64 编码後会产生字串,若图片太大,会导致编码後字串非常长,增加档案大小,因此还是建议使用 base64 编码的图不要太大,不过透过 Gzip 跟档案的 Caching 可以缓解这个问题。

以我自己的经验来说,如果是太大的图,就不会使用 Base64 编码的方式了,如果是 icon 或是缩图则会考虑使用(还是要依照各个专案当前状况决定)

举一个实际的例子,其实 google 的图片搜寻结果页面的缩图就是用 Base64 的形式喔

当点选图片缩图显示图片详细资讯时有些大图会被替换成 URL 的形式

想要更深入了解 Base64 的使用时机可以参考这篇文章

本日小结

今天介绍了不同图片格式与它们各自适合使用的场景,也说明了我认为比较重要的图片优化方式例如压缩与响应式图片。因为图片在网页资源上的占比非常高,如果能够尽量优化图片,网页载入效能将获得显着的成长。

本篇的最後,分享一个一张图片也没有却很屌的网站,一起来看看世界上的第一个网页吧!虽然真的一张图片都没有,但真的是伟大非常的第一步,看了真的好感动啊...
http://info.cern.ch/hypertext/WWW/TheProject.html

References & 图片来源

https://cythilya.github.io/2018/08/24/responsive-images/
https://httparchive.org/
https://ithelp.ithome.com.tw/articles/10252501
https://cloudinary.com/
https://blog.gtwang.org/web-development/minimizing-http-request-using-data-uri/
https://www.shareus.com/windows/image-formats-comparison-webp-vs-jpg-vs-png.html
https://blog.infolink.com.tw/2021/rediscover-pixel-dpi-ppi-and-pixel-density/


<<:  Day 9 Swift语法-进阶篇(2/5)-Inheritance

>>:  [Day5] 和应用程序有关的攻击-例外处理,重播攻击,请求伪造

组合语言跟你 SAY HELLO!!

各位点进来的朋友,你们好阿 小的不才只能做这个系列的文章,但还是分享给点进来的朋友,知道一些程序语言...

Day 30 -资料库应用小程序 订单显示(内涵程序码)

废话不多说直接开始 我们点选订单查询按钮会连结到这个表单 全域变数 static public st...

第8-1章:管理本地端主机之使用者与群组(三)

前言 在上一章节中,笔者讲解了如和切换使用者以及取得最高的root使用者权限,接下来要讲解的是本地端...

Golang 学习笔记-- 快速上手/重点整理 - 2 - var, const

print import ("fmt") fmt.Println('hello'...

CMoney工程师战斗营weekly2

在现实世界与抽象空间游走的一周 匆匆忙忙行军式的步伐迈向抽象类别的世界,老实说真的有点挫折,跟不太上...