Web性能之响应式图像与优化

图像是 Web 重要组成部分,将图像优化好了性能问题就解决了至少一半

要在 Web 网页上放置图像,我们需要使用 <img> 元素。这是一个空元素,它需要指定一个 src 属性。如果图像名为 avatar.jpg 并且它与您的 HTML 文档位于同一路径中,则可以按如下方式引入:

<img alt="avatar" src="avatar.jpg">

为了确保图像的可访问性,我们添加了 alt 属性。该属性的值应该是图片的文字描述,当图像无法显示或看不到时,作为图像的替代;例如,用户通过屏幕阅读器访问您的页面。

接下来,我们添加 widthheight 属性来指定图像的宽度和高度,也就是图像的尺寸。

<img alt="avatar" height="400" src="avatar.jpg" width="400">

在图像上指定 widthheight 属性,浏览器就知道要为这张图像保留多少空间。如果忘记指定图像的尺寸会导致布局偏移,因为浏览器不确定图像需要多少空间,布局偏移就会影响性能

但是作为一个响应式设计的网页我们图像的父级元素的大小可能不足以容纳一个固定大小的图像,

img-width-overflow

因此我们需要一个样式来使图像的大小不会超出父级元素

img {
  max-width: 100%;
}

这时又会引出另一个问题,图像的宽度变得自适应了,而高度被我们添加的 height 属性固定了,为了使图像高度也能自适应,我们需要通过样式给图像的高度指定为自动:

img {
  max-width: 100%;
  height: auto;
}

到这里我们的图像可以完美地适应父级元素的大小了,

img-width-160

img-width-240

不过我们这里又可以发现另外一个问题,给高分辨率设备提供一张低分辨率的图像会显得模糊,而给低分辨率设备提供一张高分辨率的图像又是一种浪费,我们希望能够为不同设备提供不同分辨率的图像

响应式图像

如果我们可以给一个图像容器提供多张图像让设备或浏览器自行选择合适的那一张来加载显示岂不是很美好?

<img> 元素新增了一个 srcset 属性,这是一个集合属性,通过该属性我们可以给图像提供多个源路径,使用逗号进行分隔

<!-- 提供单个图像源 -->
<img src="avatar-800w.jpg">
<!-- 提供多个图像源 -->
<img src="avatar.jpg"
     srcset="avatar-small.jpg 400w,
             avatar-large.jpg 800w">

同时,每个图像路径后面可以添加一个宽度描述符,用空格分割,比如 400w 表示 avatar-small.jpg 图像的宽度是 400 像素

宽度描述符的作用是告诉浏览器这张图像的宽度,让浏览器在还没有下载图像的情况下就能确认图像的宽度,注意宽度描述符的单位是 w

srcset 属性一起出现在标准中的还有一个 sizes 属性,这两个属性需要搭配使用才能让浏览器知道自己该加载哪张图像

<img src="avatar.jpg"
     srcset="avatar-small.jpg 400w,
             avatar-large.jpg 800w"
     sizes="400px">

sizes 属性用于告诉浏览器该图像可能的显示宽度,比如这里的值 400px 表示该图像可能的显示宽度为 400 像素,所以浏览器会加载 400 像素宽的小图像,而忽略那张 800 像素宽的大图像,毕竟图像分辨率越大理论上来讲文件体积也就越大

sizes 的值必须是一些固定有效的长度,比如 100vw, 100px 等等,不能是百分比之类的,sizes 的作用只是告诉浏览器该图像可能的显示宽度

另外 sizes 属性还支持使用媒体查询提供由逗号分隔的多个宽度值,为不同视口大小指定不同的图像可能宽度

<img src="avatar.jpg"
     srcset="avatar-small.jpg 400w,
             avatar-large.jpg 800w"
     sizes="(max-width: 600px) 400px, 800px">

sizes="(max-width: 600px) 400px, 800px" 表示视口宽度不足 600 像素的时候,图像宽度为 400 像素,此时浏览器依然是加载小的图像,而当视口宽度大于 600 像素的时候,图像宽度为 800 像素,浏览器就会加载大的图像

响应式图像除了可以适应视口的宽度,还可以适应不同的设备像素比,比如这里,当视口宽度不足 600 像素的时候,如果设备像素比为 1 则浏览器还是加载显示小的那张图像,但是如果设备像素比为 2 的话,则浏览器会加载显示大图像

总之,浏览器可以结合这几个条件从 srcset 中提供的图像中选择最合适的那张

如果浏览器不支持 srcsetsizes 属性,将会忽略它们,并使用 src 提供的图像,目前主流浏览器都支持这两个属性,详见 https://caniuse.com/srcset

传统图像格式

JPEG 是使用广泛的格式,这是一种有损格式,解码速度快,适用于照片,但是它不支持透明,所以,我们同时会使用另一种图像格式

PNG 支持透明,不过它是一种无损格式,压缩率不高,如果用于照片的话图像文件的体积往往会很大

通常情况下这两种格式我们会根据实际情况选择使用,色彩丰富的照片就考虑使用 JPEG 格式,需要透明的时候就用 PNG 格式,这两种图像格式都不支持动画

GIF 支持动...,我们现在应该没有理由还在使用 GIF

这些传统图像格式各自都有明显的缺点,为了解决这些缺点,有了接下来这些新的图像格式

现代图像格式

WebP 图像格式由 Google 创建,同时支持无损和有损压缩以及透明,动画,绝大多数情况下是比 JPEG, PNG 和 GIF 更优秀的图像格式,是设计出来取代他们的,创造至今已经得到了绝大多数浏览器支持(https://caniuse.com/webp

AVIF 支持无损和有损压缩,动画,透明,是比 WebP, JPEG, PNG 和 GIF 更优秀的图像格式,是设计出来取代他们的,目前被部分主流浏览器的新版本支持(https://caniuse.com/avif),Safari 暂时不支持,但预计不久就会,毕竟 Apple 公司是创建这种格式的组织成员之一,现在我们也可以在项目中使用了,后面会有使用方式

虽然 AVIF 比 WebP 更优秀,但是 WebP 比 AVIF 支持的浏览器更多,所以目前来说这两种格式我们都会使用到,现代图像格式的引用方式和传统图像是一样一样的,通过 <img> 元素

<img src="avatar.avif">

不过对于不支持这种格式的浏览器来说就显得无语了,好在有个新的 <picture> 元素来应对这种情况

使用 <picture> 元素可以为单个图像提供多种图像格式供浏览器选择。一个 <picture> 元素必需包含一个 <img> 元素,

<picture>
  <img alt="avatar" src="avatar.jpg">
</picture>

只是这样还没有任何特别的作用,我们还可以在 picture 元素中使用 <source> 元素为图像提供不同格式的源

<picture>
  <source srcset="avatar.avif" type="image/avif">
  <img alt="avatar" src="avatar.jpg">
</picture>

这样就比较有意思了,如果浏览器支持 AVIF 格式的图像就会加载显示 avatar.avif 文件,同时忽略该 picture 元素内其它格式,否则就会降级加载 avatar.jpg 文件

如果浏览器连 <picture> 元素都不支持,将会简单地忽略它,直接显示 <img> 元素的内容,另外还可以提供多个 <source> 元素,浏览器会自上而下地选择第一个它支持的格式

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

将需要优先显示的放在最前面就可以了,type 属性用于告诉浏览器该图像的格式,所有主流浏览器都支持 <picture> 元素 ,关于 <picture> 元素的浏览器支持可查看 https://caniuse.com/picture

响应式现代图像格式

我们注意到 <source> 元素指定资源路径的属性是 srcset,也就是说,一个 <source> 元素也是可以同时指定多个图像路径的,结合前面的内容整合到一起,一个图像元素可能就会如下所示

<picture>
  <source
    sizes="(max-width: 600px) 100vw, 50vw"
    srcset="avatar-small.avif 400w,
            avatar-large.avif 800w" type="image/avif">
  <source
    sizes="(max-width: 600px) 100vw, 50vw"
    srcset="avatar-small.webp 400w,
            avatar-large.webp 800w" type="image/webp">
  <img sizes="(max-width: 600px) 100vw, 50vw" src="avatar.jpg"
    srcset="avatar-small.jpg 400w,
            avatar-large.jpg 800w">
</picture>

图像压缩

图像压缩可以分为无损和有损两种,无损压缩就是在不损失图像质量的情况下减小图像文件的大小,如果无损压缩已经不能给我们带来多少收益的时候,我们可以考虑有损压缩,适当降低一下图像的质量,可以有效地减小图像文件的大小

我们将图像转换为 WebP 或者 AVIF 的时候,应该从源文件直接导出或者通过无损的格式(如 PNG)来转换,如果用有损的格式(如 JPEG)再来转换为有损的 AVIF 的话,那图像质量就损失两次了

延迟加载

只需要给 <img> 元素添加一个 loading="lazy" 属性即可

<img loading="lazy" src="avatar.jpg">

搭配 <picture> 元素一起食用,

<picture>
  <source srcset="avator.avif" type="image/avif">
  <img loading="lazy" src="avator.jpg">
</picture>

不支持的浏览器会自动忽略该属性,可以给不支持的浏览器提供插件来实现该功能

<img alt="avator" class="lazyload" data-src="avator.jpg" loading="lazy">

<script>
;(function() {
  if ('loading' in HTMLImageElement.prototype) {
    var images = document.querySelectorAll('img.lazyload')
    images.forEach(function(img) {
      img.src = img.dataset.src
    })
  } else {
    var script = document.createElement('script')
    script.async = true
    script.src = '//afarkas.github.io/lazysizes/lazysizes.min.js'
    document.body.appendChild(script)
  }
})()
</script>

图像解码

浏览器从下载好图像到显示出来,中间还有一个解码的过程,我们可以通过一个属性将这个过程设定为异步的

<img decoding="async" src="avatar-800w.jpg">

就这样

响应式背景图像

根据设备像素比提供不同的背景图像,需要使用到 image-set 表示法,该方法可以提供多个图像路径,并给每个路径指定一个设备像素比

.selector {
  background-image: image-set(
    url(./photo-small.jpg) 1x,
    url(./photo-large.jpg) 2x);
}

指定设备像素比用 1x, 2x 这种方式,浏览器会根据设备像素比加载对应的图像,还可以为不支持浏览器提供后备内容

.selector {
  background-image: url(./photo-small.jpg);
  background-image: image-set(
    url(./photo-small.jpg) 1x,
    url(./photo-large.jpg) 2x);
}

所以主流浏览器的最新版本都支持,参考 https://caniuse.com/css-image-set,大部分浏览器需要添加 -webkit- 前缀,更完整的写法为

.selector {
  background-image: url(./photo-small.jpg);
  background-image: -webkit-image-set(
    url(./photo-small.jpg) 1x,
    url(./photo-large.jpg) 2x);
  background-image: image-set(
    url(./photo-small.jpg) 1x,
    url(./photo-large.jpg) 2x);
}

不过浏览器厂商前缀更科学的方式是通过工具来添加,如 Autoprefixer

现代图像格式

使用现代格式的背景图像,还是使用 image-set 表示法,并指定一个图像格式,同时提供后备选项

.selector {
  background-image: url(./photo.jpg);
  background-image: image-set(
    url(./photo.avif) type("image/avif"),
    url(./photo.jpg) type("image/jpeg"));
}

同样在 image-set 中,需要优先考虑的图像放在前面,目前支持 type 描述的浏览器并不多,能支持 type 描述的同样也已经支持 AVIF 格式的图像了,所以上面的写法其实有点多余

不过因为可以为不支持的浏览器提供后备内容,所以可以放心使用

工具

posted @ 2022-02-18 10:25  by.Genesis  阅读(357)  评论(0编辑  收藏  举报