图片优化是web开发很重要的一部分,尤其是移动端的web应用。虽然图片加载不会阻碍我们页面的渲染,但是图像的快速呈现可以大大提高我们的转换率,而且很多时候图片也是我们的主角元素。

如何查看应用图片的优化情况?

可以通过 WebPageTest.org 审核网站,报告的“Image Analysis”部分列出可以更有效压缩的图像以及估计压缩能够节省的文件大小。

也可以LightHouse 对网站进行分析,找出可以优化压缩的图片.

甚至可以自己统计图像元素的加载速度,具体实现可参考之前的一篇文章如何采集和分析网页用户的性能指标

自动化压缩图片

过去我们把UI给到的图片或者蓝狐下载下来的图片放在tinypng或者imageoptim压缩,这种方法,效率很低。。

通用的压缩方法:imagemin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const imagemin = require('imagemin');
const imageminJpegtran = require('imagemin-jpegtran');
const imageminPngquant = require('imagemin-pngquant');

(async () => {
const files = await imagemin(['images/*.{jpg,png}'], {
destination: 'build/images',
plugins: [
imageminJpegtran(),
imageminPngquant({
quality: [0.6, 0.8]
})
]
});

console.log(files);
//=> [{data: <Buffer 89 50 4e …>, path: 'build/images/foo.jpg'}, …]
})();

压缩前后的效果对比 20KB VS 12KB

JPEG 压缩

JPEG 图像格式具有多种不同的压缩模式, 其中三种较为流行的模式是基线(顺序)、渐进式 JPEG (PJPEG) 及无损。

可以看到,渐进式JPEG 能够在加载时提供图像的低分辨率“预览”,从而提高感知性能,与自适应图像相比,用户可以感觉到更快的图像加载速度。
三者的压缩率 无损<基线<渐进式

如何创建渐进式 JPEG?

1
2
3
4
5
6
7
8
9
10
11
12
const imagemin = require('imagemin');
const imageminMozjpeg = require('imagemin-mozjpeg');

(async () => {
await imagemin(['images/*.jpg'], 'build/images', {
use: [
imageminMozjpeg({ quality: 65, progressive: true })
]
});

console.log('Images optimized');
})();

需要注意的是:渐进式JPEG 的解码速度比基线 JPEG 慢,有时需要长达 3 倍的时间。渐进式 JPEG 也不总是更小。 对于非常小的图像(如缩略图),渐进式 JPEG 图像可能比基线图像大。 但是,对于此类小型缩略图,渐进式渲染的效果可能并不太理想。

自动化构建

利用webpack插件imagemin-webpack-plugin,可以自动化帮我们批量压缩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import ImageminPlugin from 'imagemin-webpack-plugin'

module.exports = {
plugins: [
// Use the default settings for everything in /images/*
new ImageminPlugin({ test: 'images/**' }),
// bump up the optimization level for all the files in my `bigpngs` directory
new ImageminPlugin({
test: 'bigpngs/**',
optipng: {
optimizationLevel: 9
}
})
]
}

使用Webp

同样尺寸的图片,WebP有损文件比 JPEG 文件小 25% 至 34%,WebP 无损文件比 PNG 文件小 26%,但是WebP 的全球用户支持率大约为 74%

将图片转化为webp

1
2
3
4
5
6
7
8
9
10
const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');

imagemin(['images/*.{jpg,png}'], 'build/images', {
use: [
imageminWebp({quality: 50})
]
}).then(() => {
console.log('Images optimized');
});

兼容webp

1.使用 <picture>标记

1
2
3
4
5
<picture>
<source srcset="bg1.webp" type="image/webp">
<source srcset="bg2.jpg" type="image/jpeg">
<img src="bg.jpg" alt="背景图">
</picture>

<picture> 标记使用多个<source> 元素,并且包括一个 <img> 标记,而该标记是真正的 DOM 元素,包含图像。 浏览器循环切换源代码并检索第一个匹配项。 如果用户的浏览器不支持 <picture> 标记,则会呈现 <div> 并使用 <img> 标记。

2.使用canvas判断

1
2
3
4
5
6
7
8
9
10
11
12
 export const isSupportWebp = () => {
try {
return (
document
.createElement("canvas")
.toDataURL("image/webp")
.indexOf("data:image/webp") == 0
);
} catch (err) {
return false;
}
};

判断是否支持webp后拼装对应url向服务器获取相关图片资源

图片按需加载

图片按需加载可减少请求的压力,具体实现可参考之前的一篇文章图像延迟加载 && 列表图顺序加载

Base64与雪碧图

如果我们在 HTTP/1.x 环境中,则建议使用Base64或者雪碧图,这能减少HTTP 请求。

使用 HTTP/2 时,最好是加载单个图像,因为现在可以在单个连接中进行多次请求。

CDN(响应式)与缓存

我们可以将图片放到cdn上,这可以提高我们图片加载的速率。有很多cdn oss支持获取指定大小的图片,我们可以根据设备dpr来获取ui标注的图的大小以达到最符合大小的图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
export const coverImgSuffix = ([width, height]) => {
const devicePixelRatio = window.devicePixelRatio;
return `?x-oss-process=image/resize,m_fill,h_${Math.ceil(
height * devicePixelRatio
)},w_${Math.ceil(width * devicePixelRatio)}${compressImgSuffix()}`;
};

<ProgressiveImage
src={`${item.image}${coverImgSuffix([220,296])}`}
className={"img-selected"}
placeholder={imagePlaceHolder}
loaded={()=>{onImageShow(item.id)}}
/>

可以看到,根据不同手机dpr我们获取对应的宽高的图片。

资源可以使用 HTTP 缓存标头指定缓存策略。 具体来说,Cache-Control 可以定义缓存响应的用户及持续时间。

用video代替gif

1
2
3
4
<video autoplay loop muted playsinline>
<source src="demo.webm" type="video/webm">
<source src="demo.mp4" type="video/mp4">
</video>

使用video代替gif,可以从体积压缩和渲染上都有不错的提升!

图片预加载

如果我们的图片在页面中起着很重要的作用,想快点渲染出来,那可以使用<link rel=preload>预加载。
<link rel=preload> 是声明性提取,允许强制浏览器在不阻止文档的 onload 事件的情况下请求资源。 其可提高资源请求的优先级,否则这些资源可能会在之后的文档解析过程中才会出现。
<img><picture>、srcset 和 SVG 的图像资源都可以利用此优化策略。

注意的问题

  1. display:none 并不能避免触发图像 src 请求
  2. 但display:none 可以避免触发 background: url() 请求