图片类型、优化、处理知识点汇总
# 了解图片格式
首先,我们来对常见的图片格式做一个梳理,不同格式的图片也是影响加载的性能的重要因素,在不同的需求条件下选择不同的图片格式也非常有必要。
下面推出大多数的图片格式类型,它们皆在图片上传时可以根据类型进行判断
文件格式 | Type 类型 | 拓展名 |
---|---|---|
APNG (Animated Portable Network Graphics 动态便携式网络图像) | image/apng | .apng |
AVIF(AV1 Image File Format AV1 图像文件格式) | image/avif | .avif |
BMP(Bitmap file 位图文件) | image/bmp | .bmp |
Graphics Interchange Format 图像互换格式 | image/gif | .gif |
Microsoft Icon 微软图标 | image/x-icon | .ico, .cur |
Joint Photographic Expert Group image 联合影像专家小组图像 | image/jpeg | .jpg, .jpeg, .jfif, .pjpeg, .pjp |
Portable Network Graphics 便携式网络图像 | image/png | .png |
Scalable Vector Graphics 可缩放矢量图形 | image/svg+xml | .svg |
Tagged Image File Format 标签图像文件格式 | image/tiff | .tif, .tiff |
Web Picture format 万维网图像格式 | image/webp | .webp |
# 了解图片压缩
# 计算机表示图片的方式
首先,我们先谈谈计算机是如何表示图片的。在标准计算机中使用的色彩模型为 RGB 模型,也就是我们 css 中常写到的模型 rgb(Red, Green, Blue)。图片是由多个像素点组成,图像中的每个像素都存储了三个值,范围从 0~255。其中某个值越大,其对应的颜色就有着越大的权重(假设每个色彩分量都用 8 个比特(即 1 字节)来表示,那么一个像素点就占了 3 个字节(Red+Green+Blue))。计算机需要先将我们的图片通过复杂的解码操作,将其从一个(例如.jpg)图片文件还原成计算机原有的 RGB 格式以便计算机渲染。那么流程就可以看作以下步骤:
RGB 模型组合 --> Encoder 组件编码 --> 生成.jpg 图片 --> Decoder 组件解码 --> 还原为 RGB 模型组合
在这个解码的过程中,我们其实并不需要还原得跟未压缩的原始图片一模一样,在有限的情况下并不会对人的视觉产生影响,但却减小了文件的体积。jpeg 的体积相较于 jpg 更小的原因就是其分别定义了编码和解码的流程,在作业流程的压缩部分有意的丢失部分信息,这种对图片的处理方式,我们通常称呼其为:有损压缩。
# 有损压缩
有损数据压缩(英语:lossy compression)是一种数据压缩方法,经过此方法压缩、解压的数据会与原始数据不同但是非常接近。有损数据压缩又称破坏性资料压缩、不可逆压缩。有损数据压缩借由将次要的数据舍弃,牺牲一些质量来减少数据量、提高压缩比。根据各种格式设计的不同,有损数据压缩都会有代间损失 —— 每次压缩与解压文件都会带来渐进的质量下降。
有损压缩的一种方式即是利用了人类对颜色的判断并没有那么敏感的缺陷:假设一张图片由 8*8 个像素点组成,通过复杂的计算,损失颜色空间,细化亮度空间的维度,将其合并为 4*4 个像素点的图片, 并有策略的去除图片中不太重要、较少见的高频信号来达到更高的压缩率。在真实的场景中,一张图片通常拥有上万个像素点,人通过肉眼基本分辨不出抽样后的图片相较之前有何差异。推荐观看视频来了解有损压缩更细枝末节的内容:JPEG 不可思议的压缩率——归功于信号处理理论 (opens new window)
通常有损压缩大多出现在 jpeg 类型,以满足更小的内存占用和更快的解码速度。
# 无损压缩
无损数据压缩(Lossless Compression),是指资料经过压缩后,信息不被破坏,还能完全恢复到压缩前的原样。相比之下,有损数据压缩只允许一个近似原始资料进行重建,以换取更好的压缩率。 无损数据压缩在许多应用程序中使用。例如,ZIP 和 gzip。无损压缩的方法可以通过一些编码手段,用结构化的数据来减少对重复信息的磁盘占用,针对图片来说减少了图片在磁盘上的空间占用。但是并不能减少图像的内存占用量,这是因为,当从磁盘或网络请求上获取图像时,浏览器又会对图片进行解码,把丢失的像素用适当的颜色信息填充进来。
举个例子,当我们在看片的时候,我们按顺序走,先看了悠亚的,再看了某萌的,当我们再次看到悠亚的时,我们可以认为第一部和第三部都是同一个人出演的,这时候我们可以用第一部来代替第三部,于是我们通过一个替代的规则:(距离,长度)为第三部片子打上标记,假设第一部是 1,第二部是 2,从第三部到第一部要向前一步,距离就为 1,第一部的标记长度为 1,那么第三部的标记则为 (1,1) ,即向前一步,向前读取一位,但原本只需要标记为 3 的第三部戏被标记成了 (1,1) ,从某种程度上来说,有可能会出现越压越大的情况。
对于无损压缩而言,有一个概念“熵”,可以理解为一个东西的复杂程度。以扑克牌为例,总共 54 张牌,而除去相同的点数,只有 15 种牌,那它的复杂程度,也就是“熵”,就是 15,压缩是有一个定律就是压缩的大小不可能小于熵的大小,所以如果一个文件有 1GB,但这 个文件的熵为 99MB,那么它完全有可能压缩到 100MB。
# 图片优化
图片的优化分为加载和显示两个阶段
# 加载阶段
# 减小体积
减小体积的最好办法就是上文说到的有损压缩,减少请求耗时,让浏览器更快拿到内容进行绘制。
# 减小内存占用
内存占用和图片体积不等同,两张不同体积的图片可能有着相同的内存占用,因此优化内存占用可以让浏览器解码图片和光栅化的时间减少,因为不需要计算绘制那么多的图片信息。光栅化时间的减少直接影响了页面的渲染速度,以及页面的卡顿。
# 显示阶段
# 添加占位
添加图片加载时的占位和图片错误时的占位图,能够提升用户的体验,减少等待期间的用户流失。
# 懒加载
减少同屏下过多的请求导致页面白屏等问题,减少不必要的资源请求,减少内存占用。
# 最常见的方式是采用 js 实现懒加载:
- 监听 onscroll 事件,通过 getBoundingClientRect API 获取元素图片距离视口顶部的距离,配合当前可视区域的位置实现图片的懒加载
- 通过 IntersectionObserver API,Intersection Observer(交叉观察器) 配合监听元素的 isIntersecting 属性,判断元素是否在可视区内,能够实现比监听 onscroll 性能更佳的图片懒加载方案
# 当前有一个新的 CSS 属性:content-visibility: auto,也可以实现图片懒加载。
content-visibility:属性控制一个元素是否渲染其内容,它允许用户代理(浏览器)潜在地省略大量布局和渲染工作,直到需要它为止。利用 content-visibility 的特性,我们可以实现如果该元素当前不在屏幕上,则不会渲染其后代元素。该属性的作用远不止用于图片的懒加载,也可以利用在长列表的懒加载等多方面。
但是 content-visibility 有一个缺点,其虽然没有在页面内立即渲染,但是其内部所需要的资源仍在页面一开始就触发加载。并且其当前的浏览器兼容还较低。并且在图片不断加载的过程中页面的滚动条会出现一定的鬼畜现象(图片加载之后撑高)。
# 使用属性 loading=lazy 进行懒加载
属性的值为 loading=lazy 会告诉浏览器,如果图像位于可视区时,则立即加载图像,并在用户滚动到它们附近时获取其他图像。当然该属性也有一定的兼容性问题。
<img src="xxx.png" loading="lazy" />
其可以配合 decoding=async 实现图片的异步解码
<img src="xxx.png" loading="lazy" decoding="async" />
浏览器便会异步解码图像,加快显示其他内容。
# 格式回退
对于不同浏览器的对格式的支持不同,选择不同格式的图片进行展示,达到优雅降级的目的。例如不支持 webp 时自动降级成 png(这里需要 ui 提供几种格式的图片) 例如这里我们可以使用 picture 标签帮我们做自动降级的处理,并且最终的加载事件仍会落在 img 标签上,事件的监听仍会生效。
<picture>
<source srcset="{avif}" type="image/avif" />
<source srcset="{webp}" type="image/webp" />
<img src={image} onError={() => onError?.()} {...remain} />
</picture>
2
3
4
5
# 适配不同的屏幕尺寸及 DPR
首先我们需要了解一下什么是 DPR。要了解 DPR,首先需要知道什么是设备独立像素 以及 物理像素。
# 设备独立像素
以 iPhone6/7/8 为例,这里我们打开 Chrome 开发者工具:这里的 375 * 667 表示的是什么呢,表示的是设备独立像素(DIP),也可以理解为 CSS 像素,也称为逻辑像素:
设备独立像素 = CSS 像素 = 逻辑像素
如何记忆呢?这里使用 CSS 像素来记忆,也就是说。我们设定一个宽度为 375px 的 div,刚好可以充满这个设备的一行,配合高度 667px ,则 div 的大小刚好可以充满整个屏幕。
# 物理像素
当我们去购买一款手机的的时候,我们都可以看到手机的一些参数,比如分辨率: iPhone7 的分辨率是 1334 x 750,这里描述的就是屏幕实际的物理像素。物理像素,又称为设备像素。显示屏是由一个个物理像素点组成的,1334 x 750 表示手机分别在垂直和水平上所具有的像素点数。通过控制每个像素点的颜色,就可以使屏幕显示出不同的图像,屏幕从工厂出来那天起,它上面的物理像素点就固定不变了,单位为 pt。
设备像素 = 物理像素
# DPR(Device Pixel Ratio) 设备像素比
设备像素比描述的是未缩放状态下,物理像素和设备独立像素的初始比例关系。
简单的计算公式:
DPR = 物理像素 / 设备独立像素
我们套用一下上面 iPhone7 的数据(取设备的物理像素宽度与设备独立像素宽度进行计算):
iPhone7’s DPR = iPhone7’s 物理像素宽度 / iPhone7's 设备独立像素宽度 = 2
750 / 375 = 2 或者是 1334 / 667 = 2
可以得到 iPhone7 的 dpr 为 2。也就是我们常说的视网膜屏幕。
视网膜(Retina)屏幕是苹果公司"发明"的一个营销术语。苹果公司将 dpr > 1 的屏幕称为视网膜屏幕。
# 为不同的 DPR 的屏幕提供不同的图片
DPR 和图片适配有什么关系呢?在相同的设备独立像素大小下,不同的大小的图片渲染出来的效果是不同的。 我们以 dpr=3 为例,假设有一张 667*375 的图片,以及其 2/3 倍图,在 dpr=3 的手机上, 3 倍图将会是最清晰的。因此,为了在不同的 DPR 屏幕下,让图片看起来都不失真,我们需要为不同 DPR 的图片,提供不同大小的图片。
# 方案一:使用多倍图
无论 dpr 是多少的手机,所有使用的图片都使用高倍图,这样的好处是无论什么手机都能清晰的显示图片内容,缺点是高倍图的更大体积会造成带宽的浪费,如果是一个多张大图的小程序会对用户体验有较大影响。
# 方案二:媒体查询
我们可以通过相应的媒体查询,得知当前的设备的 DPR 值,这样,我们就可以在对应的媒体查询中,使用对应的图片,例如:
#id {
background: url(xxx@2x.png)
}
@media (device-pixel-ratio: 2) {
#id {
background: url(xxx@2x.png)
}
}
@media (device-pixel-ratio: 3) {
#id {
background: url(xxx@3x.png)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
缺点在于,这样需要写对应 dpr 的样式太多了,比如 Nexus 5X 的 dpr 为 2.65,不好穷举所有的 dpr 场景,伴随着新 dpr 的入场还得对应去修改代码。
# CSS 配合 image-set 语法
image-set 属于 CSS background 中的一种语法,image-set() 函数为设备提供最合适的图像分辨率,它提供一组图像选项,每个选项都有一个相关的 DPR 声明,浏览器将从中选择最适合设备的图像进行设置。
.img {
/* 不支持 image-set 的浏览器*/
background-image: url('../photo@2x.png');
/* 支持 image-set 的浏览器*/
background-image: image-set(
url('./photo@2x.png') 2x,
url('./photo@3x.png') 3x
);
}
// 如果其设备对应的 DPR 为 2,会选取这条 url('./photo@2x.png') 2x 记录,也就是最终生效的 URL 是 './photo@2x.png';
// 如果其设备对应的 DPR 为 3,会选取这条 url('./photo@3x.png') 3x 记录,也就是最终生效的 URL 是 './photo@3x.png';
2
3
4
5
6
7
8
9
10
11
12
13
其缺点也是难以匹配所有情况。
# srcset 配合 1x 2x 像素密度描述符
<div class='illustration'>
<img src='illustration-small.png'
srcset='images/illustration-small.png 1x,
images/illustration-big.png 2x'
>
</div>
2
3
4
5
6
上面 srcset 里的 1x,2x 表示 像素密度描述符,表示
- 当屏幕的 dpr = 1 时,使用 images/illustration-small.png 这张图
- 当屏幕的 dpr = 2 时,使用 images/illustration-big.png 这张图
- 如果不支持 srcset 语法,src='illustration-small.png' 将会是最终的兜底方案
# 关于<Img>元素
- <img> 是一个可替换元素。它的 display 属性的默认值是 inline,但是它的默认分辨率是由被嵌入的图片的原始宽高来确定的,使得它就像 inline-block 一样,可以为其设置 block 生效的属性。
- <img> 元素并没有基线(baseline)。意味着当其行内上下文设置的基线位置为 baseline 时( vertical-align: baseline),它在同一行内遇上其他的行内(块)元素,图像的底部将会与容器的文字基线对齐
- 额外的,除了 src 属性,<img> 还有 srcset 属性,在高分辨率设备上,它将被优先加载,取代 src 属性中的图像。
# 图片的宽高比、裁切、缩放
# 使用 aspect-ratio 限制宽高比
通常,我们书写 img 样式的时候,我们都会为图片增加固定的宽高,以免因为图片的不同尺寸从而导致撑开元素破坏布局。例如:
img {
width: 150px;
aspect-ratio: 3 / 2; // 宽 / 高
}
// 等同于
img {
width: 150px;
height: 100px;
}
2
3
4
5
6
7
8
9
# 使用 object-fit 控制图片拉伸
写过小程序的朋友们也会经常看到 object-fit 这个属性它能够指定可替换元素的内容(图片)该如何适应它的父容器的高宽。
其具体作用看 MDN 文档:https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit 语法
object-fit: contain;
object-fit: cover;
object-fit: fill;
object-fit: none;
object-fit: scale-down;
/* Global values */
object-fit: inherit;
object-fit: initial;
object-fit: revert;
object-fit: revert-layer;
object-fit: unset;
2
3
4
5
6
7
8
9
10
11
object-fit 还有一个配套属性 object-position (opens new window),它可以控制图片在其内容框中的位置。(类似于 background-position),默认是 object-position: 50% 50%,如果你不希望图片居中展示,可以使用它去改变图片实际展示的 position。
# 使用 image-rendering 设置图片缩放
image-rendering 属性能让图像在缩放时,提供不一样的渲染方式,让图片的展示形态更为多样化,或者说是尽可能的去减少图片的失真带来的信息损耗。例如我之前做的一个手机官网项目,其整个页面是一个 rem 为单位的页面布局,图片有时候会出现模糊的情况,这时候就要使用 image-rendering 的-webkit-optimize-contrast 属性来进行缩放,最大程度的降低其模糊的质感。但是实际上,现代浏览器基本暂时只支持:auto、pixelated、以及 -webkit-optimize-contrast(Chrome 下的 smooth)
- image-rendering: auto:自 Gecko 1.9(Firefox 3.0)起,Gecko 使用双线性(bilinear)算法进行重新采样(高质量)。
- image-rendering: smooth:使用能最大化图像客观观感的算法来缩放图像
- image-rendering: high-quality:与 smooth 相同,但更倾向于高质量的缩放。
- image-rendering: crisp-edges:必须使用可有效保留对比度和图像中的边缘的算法来对图像进行缩放,并且,该算法既不会平滑颜色,又不会在处理过程中为图像引入模糊。合适的算法包括最近邻居(nearest-neighbor)算法和其他非平滑缩放算法,比如 2×SaI 和 hqx-* 系列算法。此属性值适用于像素艺术作品,例如一些网页游戏中的图像。
- image-rendering: pixelated:放大图像时,使用最近邻居算法,因此,图像看着像是由大块像素组成的。缩小图像时,算法与 auto 相同。
# 最后
文章借鉴:
coco: https://mp.weixin.qq.com/s/sTlgjI4rOfrPRna0iLiRRA