目前的前端图片加载优化技术有很多,像懒加载/预加载,img上的srcset属性以及picture标签,新的图片编码格式以及Client Hints等。
Client Hints
顾名思义,client hints是指通过客户端信息来进行资源选择的一种方法,由于用在图片加载上,所以这里的客户端信息一般是指图片宽度,DPR(device pixel ratio)和视窗宽度(viewport width)。其基本原理是在http传输中加入相关header,让服务器好选择一张合适的图片返回给客户端。
如何使用
要使用client hints,需要在添加一个meta标签,内容如下:
<meta http-equiv="Accept-CH" content="DPR, Viewport-Width">
然后当浏览器向服务器请求图片的时候,就会附带上两个额外的header:
DPR: 2
Viewport-Width: 535
由此可知,客户端的像素密度是2,Viewport的宽度是535像素,这样服务器就能通过客户端信息来发送不同的图片了,这些图片既可以是已经存储的不同分辨率的图片,也可以即时地对图片大小进行转换,这取决于存储和计算之间的权衡。
兼容性
Cient Hints不是一个很新的东西,但它的兼容性却一般,可以作为一种渐进式用户体验的方法。 下面是它的浏览器兼容性:
image.png 可以看到,除了Chrome和Opera以及新版Edge之外全军覆没,所以这种方法见过的人少,用过的人更少了。下面就介绍一个一种更常用的方法。
Responsive images
如果说client hints是在服务器上对图片进行区分,那么Responsive images就是将这个过程放到浏览器上了。一般来说,组成Responsive images的标签和属性有img标签上的srcset,sizes属性,以及picture和source标签。
如何使用
关于srcset和sizes
理解这两者的最好方法就是用一个简单的例子,下面这个例子来自MDN:
<img srcset="elva-fairy-480w.jpg 480w,
elva-fairy-800w.jpg 800w"
sizes="(max-width: 600px) 480px,
800px"
src="elva-fairy-800w.jpg"
alt="Elva dressed as a fairy">
首先,sizes决定了一个叫插槽的东西,顾名思义,就是留给这个图片的空间是多少,假设我放置这个图片的容器是800px,需要填满这个容器,那么就应该写成sizes=“800px”,同时,sizes支持使用媒体查询来区分不同的slot,不同的slot使用逗号分开,如上面的写法,意思就是当viewport宽度大于600px的时候,slot宽度为800px,否则为480px,当没有图片刚好匹配slot宽度时,浏览器会选择第一张比slot宽的图片。
其次,srcset决定了可以使用的图片来源,如上面的写法,就是说浏览器可以按照slot的宽度从宽度分别为480px和800px的两张图片中自动进行选择。
最后,src属性是当浏览器不支持srcset时候进行回退使用。
需要注意的是,srcset每张图片后面跟的宽度单位是w,代表图片文件的实际宽度,也即img.naturalWidth的宽度,为什么要用原生分辨率呢,其实道理很简单,我们可以想一下如果图片后面跟着的是css pixel的话,那么实际上浏览器是无法知道最合适的图片的,因为不同设备的DPR不一样,假设当前设备DPR为1,srcset=“elva-fairy-480w.jpg 480px”,表明想在slot宽度为480px的时候使用这张图片,但如果另一台设备DPR为2,很显然图片就会糊了,这样就达不到想要的效果了。所以最好的方式是告诉浏览器图片的原始分辨率,让它通过DPR和slot宽度自行判断需要使用哪个图片。
不需要使用sizes的情况
实际上,sizes并不是必须的,我们知道img作为替换元素,在没有css规定的情况下,它的大小由内容(也就是图片本身决定),也就是说浏览器在实际加载完成图片之前,都是无法知道img占的位置大小的,所以才需要sizes这个属性显式地规定slot的宽度,也就是程序员期望加载完图片之后占的宽度。
但如果使用css规定了img的大小:
img {
width: 300px;
height: auto;
}
这样的话,浏览器就能够知道img占的宽度,sizes也就变得不是必须了。
关于picture标签
picture标签实现的是根据媒体选择区分不同图片来源的功能:
<picture>
<source media="(max-width: 799px)" srcset="elva-480w-close-portrait.jpg">
<source media="(min-width: 800px)" srcset="elva-800w.jpg">
<img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva">
</picture>
其中source标签标明可以选择的图片来源,浏览器会使用第一个匹配的source作为图片来源。如果像上面这样写,那么窗口宽度在大于等于800px的时候会使用第二个source,否则使用第一个source。
在最后一个source后面要加上一个img标签,一是为了在没有source匹配时有默认显示的图片,二是当浏览器不支持picture时可以平稳回退。
picture和srcset的区别
从上面的代码来看,picture和srcset实现了相似的功能,但实际上它们是很不一样的,从浏览器匹配的逻辑来看可以很明显地区分它们:
picture: media -> source
srcset: media -> slot -> source
可以看到srcset匹配的过程中多了一个slot,也就是说srcset匹配的图片依据的是图片所占空间,而picture匹配依据的是屏幕宽度。
同时picture可以根据图片格式进行选择:
<picture>
<source srcset="mdn-logo.svg" type="image/svg+xml">
<img src="mdn-logo.png" alt="MDN">
</picture>
如果浏览器不支持svg,那么就会使用img中的png图像。
兼容性
从兼容性来看,Responsive images明显比Client Hints好的多,除了不争气的IE以外大部分新浏览器都支持。
图片格式
图片格式是优化图片加载的重要方式之一,除了常用的png/jpeg图像之外,现在常用的高压缩比+高质量的图片编码格式有WebP和AVIF等。
WebP
webp是Google搞得一种基于vp8视频帧编码的图片编码格式,前期兼容性一般,现在除了IE和safari之外的新浏览器大部分都支持:
AVIF
AVIF由开源组织AOMedia开发,Netflix、Google与Apple均是该组织的成员
有大公司撑腰,AVIF可以说前景比较光明,但其目前的原生兼容性不佳:
不过好在其有一个polyfill:Kagami/avif.js
这个polyfill使用service worker劫持fetch事件然后对avif图片进行处理,所以页面中的img标签可以直接引用avif文件:
<body>
<!-- Register worker -->
<script src="reg.js"></script>
<!-- Can embed AVIF with IMG tag now -->
<img src="image.avif">
<!-- Or via CSS property -->
<div style="background: url(image2.avif)">
some content
</div>
</body>
当然,前提是需要浏览器支持service worker。