字不如表,表不如图。在前端开发中我们的网站上60%都是图片,所以图片的数量是很大的。
图像问题优化主要分为:图像的选取和使用,图像的加载和显示
一,图像的选取
如果能用css样式可以表示出来图片所展示的重要信息和内容,就尽量不要使用图片。实在不能用css样式表示出来再使用图片。
矢量图和位图
- 1.1矢量图
矢量图中的图形元素呗定义为一个对象,主要用作品牌logo,控件图标,二维码,及一些简单图形
优点:能够在任何缩放比例下都可以呈现出细节同样清晰的展示效果
缺点:细节展示不够丰富,对复杂的图像若通过SVG进行矢量图绘制想要达到照片的效果文件会大的离谱
ps:SVG(scalable vector graphics)可缩放的矢量图形,是一种基于XML的图像格式.
- 1.2位图,是通过对一个矩阵中的栅格进行编码来表示图像的,组成图像的栅格像素点越多且每个像素点所表示的颜色越广.对于云朵及波浪的细节表现位图效果更好
一个像素点四个通道(rgba)就是4字节,当图像尺寸为100×100像素时,文件大小为39KB,10000个像素点×4字节 = 39.0625KB
分辨率
在前端开发中书写css时,经常会为图像设置显示所需长宽像素值,但在不同设备上,有时候相同图像及相同设置,其渲染的图像清晰度会有差距,那么主要就是涉及到两种分辨率:图像分辨率和屏幕分辨率
- 图像分辨率:就是该图像文件所包含的真实像素值信息,如200×200像素分辨率的图像文件,它就定义了长宽各200个像素点的信息
- 屏幕分辨率:就是显示器屏幕所能显示的最大像素值。如一个10×10像素的图像,可以使用10×10,20×20,40×40像素的设备分辨率来显示,但屏幕分辨率越高图像越清晰。
这里优化图片有一个img标签中的属性,是srcset
属性可以用来针对不同设备,提供不同的分辨率图像.
语法格式:
<img src='1.jpg' srcset="photo@2x.jpg 2x,phott@3x.jpg 3x">
使用picture标签可以进行多图像文件选择,屏幕方向、设备大小、屏幕分辨率等
语法格式:
<picture>
<source media="{min-width:800px}" srcset="photo.jpg,photo-2x.jpg 2x">
<source media="{min-width:450px}" srcset="photo-small.jpg,photo-small 2x.jpg 2x">
</picture>
一个真实的场景:用户上传了一张高清图片,这张图片的展示媒介包括27英寸的iMac、15寸的Macbook Pro、各种尺寸的平板、各种分辨率的Windows本、各种尺寸和DPR的手机,如何保证图片在不同的媒介上都足够高清、图片体积尽可能小呢?
如iphone12pro的设备像素(分辨率)1170 x 2532
屏幕像素密度PPI(pixel per inch):用于代表屏幕上每英寸可以显示的像素点的数量:当设备尺寸相同,但像素变得更密集时,屏幕能显示的画面过渡更细致,图像看起来就更清晰明快。
设备像素:设备像素指的是显示器上的真实像素,每个像素的大小是屏幕固有的属性,屏幕出厂以后就不会改变了。
逻辑像素:就是css像素 长宽,如
.box {
width:200px;
height:200px;
}
像素比:dpr = 同一方向上的 设备像素 / CSS像素 (缩放比是1的情况)
所以iPhone12pro的像素比就是1170 x 2532/390 x 844 = 3
假设一张图片水平铺满屏幕,在CSS里可以设置img { width: 390px },而图片的原始宽度至少是1170px才能保证高清显示
图片的原始宽度 >= CSS里设置的宽度 x DPR这样才能高清显示
代码就应该写成
`<img src='1.jpg' srcset="photo@2x.jpg 3x">才可以高清显示
但在实际开发中我们的图片要适应网页端和移动端,所以根据实际尺寸来选择图片,写法如下
以IPhone11 (828 x 1792, DPR是2)和 Macbook Pro (2880 x 1800, DPR是2) 为例,二者的DPR相同。但如果一张图片想全屏高清展示在Macbook Pro上,实际宽度至少要是2880px,而IPhone11上只需要828px的图片即可实现高清全屏展示。
所以根据尺寸来选择显示什么图片
<img src="image.jpg"
srcset="image_S.jpg 600w,
image_M.jpg 900w,
image_L.jpg 1500w,
image_XL.jpg 3000w"/>
`如果需要实际宽度是600px的图片就加载imageS.jpg,如果需要实际宽度是2800px的图片就加载imageXL.jpg。需要实际宽度是多少该如何计算呢?
浏览器会用屏幕逻辑像素的宽度(screen.width) 乘以 DPR(window.devicePixelRatio) 计算出结果,再从列表里选一个最靠近计算结果的值对应的图片。
那么实现以下布局:
视口宽度大于900px,则每个图像的固定宽度为300px。
视口宽度在700px~900px,每个图像占用33vw,即总视口宽度的33%宽度。
视口宽度在450px~700px,每个图像占用50vw,即视口总宽度的50%。
视口宽度小于450像素,每个图像占用100vw,即整个视口宽度。
<!DOCTYPEhtml>
<html lang="en">
<head>
<meta name="viewport"content="width=device-width">
<style>
img{width: 300px;}
@media(max-width: 1000px){
img{width: 33vw;}//屏幕大于1000px 设置占总屏幕的33%
}
@media(max-width: 700px){
img{width: 50vw;}
}
@media(max-width: 450px){
img{width: 100vw;}
}
</style>
</head>
<body>
<img src="https://static.xiedaimala.com/images/study-map.png?imageView2/2/w/1200"
srcset="https://static.xiedaimala.com/images/study-map.png?imageView2/2/w/300 300w,
https://static.xiedaimala.com/images/study-map.png?imageView2/2/w/500 500w,
https://static.xiedaimala.com/images/study-map.png?imageView2/2/w/1000 800w,
https://static.xiedaimala.com/images/study-map.png?imageView2/2/w/1200 1200w"//后面的1200w 是所需实际宽度
sizes="(max-width: 450px) 100vw, (max-width: 700px) 50vw, (max-width: 1000px) 33vw, 300px">
</body>
</html>
明明在CSS里已经做了类似的设置,为什么还要在img标签里再写一遍?那是因为浏览器没办法在解析img标签时提前知道该图片在CSS里设置的布局是怎样的(假设能知道,就得要求在CSS全部解析之后才能加载图片,太迟了)。
sizes属性做了响应式设置后,浏览器就计算图片的大小,再根据DPR计算出需要加载图片的原始尺寸。从srcset里配置图片列表里选出最接近的一个执行加载。
像素的增加对于同样大小的网页图像,在旧手机里全屏展示,在新手机里却只需要一半的屏幕就显示出来了,剩下的另一半屏幕将成为空白,这降低了web用户的体验。
在JavaScript里可以通过window.devicePixelRatio获取到用户设备的DPR值。
对应到web开发中,就是需要根据不同的DPR缩放网页内容,计算公式为:
图片缩放尺寸=图片逻辑像素x DPR
具体以iPhone6为例,其屏幕宽度的物理像素共750个,逻辑像素是375px,所以DPR = 750/375= 2,为了适配iPhone6,应该使用2倍大小的图片进行展示。
搞明白 dips,我们举个简单例子,MacBook Pro 13.3 英寸的显示器分辨率是 2560 x 1600,这个 2560px 就是我们前面说的设备上的物理像素值,而浏览器全屏显示的宽度只有 1280px,这个就是 dips 值,最终可以知道这台 MacBook Pro 电脑屏幕的 DPR 为 2,DPR 在这里所表达的意思就是:1280 dips 在实际显示的时候,被硬件扩展到了 2560 的硬件像素宽度,2 个物理像素对应 1 个 CSS 像素(这个指的水平方向或垂直方向,如果在一个平面内的话 4 个物理像素点对应 1 个 CSS 像素点)。
如果现在上面这台电脑里有一张实际宽度为 200px 的高清图片,在浏览器里被 css 设置宽度为 200px,那么这张图片看起来就会有点模糊,因为它实际被硬件扩展到了 400px 的硬件像素宽度,是它实际宽度的两倍。但是,如果它被 css 设置宽度为 100px,这时候它实际被硬件扩展到了 200px 的硬件像素宽度,和它实际像素一致,就不会模糊了。
二,图片的格式
2.1JPEG
JPEG是所有图像格式中出现最早,同时是使用范围最广的一种格式。他是一种有损压缩算法
用作:
- 背景图,轮播图,或商品图片,但是logo需要较强线条感和强烈的颜色对比,ipeg会出现边界模糊并且不支持透明度
1压缩模式
JPEG包含了多种压缩模式,常见的有基线的,渐进式的。
- 基线模式加载顺序是自上而下的,当网络缓慢或不稳定时,是从上往下加载的
- 渐进式模式是将图像文件多次扫描,首先展示一个低质量的图像,随着扫描到的图像信息不断增多,每次扫描过后展示的图像清晰度也不断提升
其优点是:网络缓慢的情况下可以快速加载一个图像质量比较模糊的预览版本,这样用户就可以了解大致内容
缺点是:解码速度要比基线式的慢一点
2.创建渐进式JPEG
如果所得到的图像不是渐进式 JPEG,那么我们可以通过许多第三方工具来进行处理,例如 imagemin、libjpeg、imageMagick 等。值得注意的是,这个步骤应当尽量交给构建工具来自动化完成,例如通过如下代码可以将该工作加入gulp 处理管道
const gulp = require('gulp')
const imagemin = require('gulp-imagemin')
gulp.task('images', () => {
gulp.src('image/*.jpg')
.pipe(imagemin({
progressive: true
}))
.pipe(gulp.dest('dist'))
})
其它 JPEG 编码方式
除了常见的基线与渐进式压缩编码方式,还有几种现代的 JPEG 编码器,它们尝试以更高的保真度及压缩后更小的文件大小为目标,同时还兼容当前主流的浏览器。其中比较出色的有 Mozilla 基金会推出的 MozJPEG 和 Google 提出的 Guetzli。
MozJPEG 和 Guetzli 也都已经有了可靠的 imagemin 插件支持,其使用方式与渐进式 JPEG 处理方式类似,这里仅列出示例代码,具体工程化构建请结合项目实践自行改写:
const gulp = require('gulp')
const imagemin = require('imagemin')
const imageminMozJPEG = require('imagemin-mozjpeg') // 引入 MozJPEG 依赖包
const imageminGuetzli = require('imagemin-guetzli') // 引入 Guetzli 依赖包
// MozJPEG 压缩编码
gulp.task('mozjpeg', () => {
gulp.src('image/*.jpg')
.pipe(imagemin({
imageminMozJPEG({ quality: 85 })
}))
.pipei(gulp.dest('dist'))
})
// Guetzli 压缩编码
gulp.task('guetzli', () => {
gulp.src('image/*.jpg')
.pipe(imagemin({
imageminGuetzli({ quality: 85 })
}))
.pipei(gulp.dest('dist'))
})
- 一般的图片在使用一些外部工具找到图像的最佳表现质量后,再用 MozJPEG 进行编码压缩。
- 如果不介意编码时间长,可以使用 Guetzli 会获得更高质量的图像。
2.2GIF
GIF(graphics interchange format)的缩写,一般用作动画
优点
1单帧的GIF转化为png
首先可以使用npm引入ImageMagick工具来检查GIF图像文件,看是否包含多帧动画,如GIF不包含多帧动画会返回GIF字符串,包含则返回多帧信息
const im = require('imagemagick');
//检查是否为动画
im.identify(['-format','%m','my.gif'],(err,output)=>{
if(err)throw err;
})
//将gif转换为png
im.convert(['my.gif','my.png'],(err,stdout)=>{
if(err)throw err;
console.log("转换完成",stdout)
})
2 gif动画优化
由于动画包含了许多静态帧,并且每个静态帧图像上的内容在相邻的不同帧上通常不会有太多的差异,所以可通过工具来移除动画里连续帧中重复的像素信息。这里可借助 gifsicle 来实现。
const {execFile} = require('child_process');
const gifsicle = require('gifsicle');
execFile(gifsicle,['-o','output.gif','input.gif'],err=>{
console.log('动画压缩完成')
})
当了解过 GIF 的相关特性后,不难发现如果单纯以展示动画这个目的来看,那么 GIF 可能并不是最好的呈现方式,因为动画的内容将会受到诸如图像质量、播放帧率及播放长度等因素的限制。
GIF 展示的动画没有声音,最高支持 256 色的图像质量,如果动画长度较长,即便压缩过后文件也会较大。综合考虑,建议将内容较长的 GIF 动画转化为视频后进行插入,因为动画也是视频的一种,成熟的视频编码格式可以让传输的动画内容节省网络带宽开销。
可以利用 ffmpeg 将原本的 GIF 文件转化为 MPEG-4 或 WebM 的视频文件格式,例如一个 14MB 的 GIF动画通过转化后得到的视频文件格式大小分别是:MPEG-4格式下 867KB,WebM 格式下 611KB。
另外,要知道通过压缩后的动画或视频文件,在播放前都需要进行解码,可以通过 Chrome 的跟踪工具(chrome://tracing)查看不同格式的文件,在解码阶段的 CPU 占用时,文件格式与 CPU 耗时如表所示:
文件格式 | CPU耗时(ms) |
---|---|
GIF: | 2,668 |
MPEG-4: | 1,995 |
WebM-- | 2,354 |
从表中可以看出,相比视频文件,GIF 在解码阶段也是十分耗时的,所以出于对性能的考虑,在使用GIF前应尽量谨慎选择。
2.3 png
PNG 是一种无损压缩的高保真图片格式,它的出现弥补了 GIF 图像格式的一些缺点,同时规避了当时 GIF 中还处在专利保护期的压缩算法,所以也有人将 PNG 文件后缀的缩写表示成“PNG is Not GIF”。
相比于 JPEG,PNG 支持透明度,对线条的处理更加细腻,并增强了色彩的表现力,不过唯一的不足就是文件体积太大。如果说 GIF 是专门为图标图形设计的图像文件格式,JPEG 是专门为照片设计的图像文件格式,那么 PNG 对这两种类型的图像都能支持。通常在使用中会碰到 PNG 的几种子类型,有 PNG-8、PNG-24、PNG-32 等。
2.4webp
前面介绍的三种图像文件格式,在呈现位图方面各有优劣势:GIF 虽然包含的颜色阶数少,但能呈现动画;JPEG 虽然不支持透明度,但图像文件的压缩比高;PNG 虽然文件尺寸较大,但支持透明且色彩表现力强。
开发者在使用位图时对于这样的现状就需要先考虑选型。假如有一个统一的图像文件格式,具有之前格式的所有优点,WebP 就在这样的期待中诞生了。
WebP 是 Google 在 2010 年推出的一种图像文件格式,它的目标是以较高的视觉体验为前提的,尽可能地降低有损压缩和无损压缩后的文件尺寸,同时还要支持透明度与动画。根据 WebP 官方给出的实验数据,在同等 SSIM 质量指数下,使用 WebP 有损文件,文件尺寸会比 JPEG 小25%~34%,而使用 WebP 无损文件,文件尺寸会比 PNG 小 26%。对于可以接收有损 RGB 压缩的情况,有损 WebP 还支持透明度,通常提供比 PNG 小 3 倍的文件大小。
如何使用 WebP
可以使用图像编辑软件直接导出 WebP 格式的图像文件,或者将原有的 JPEG 或 PNG 图像转化为 WebP 格式。这样的转化最好使用构建工具辅助完成,比如通过 npm 安装 webp-loader 后,在 webpack 中进行如下配置:
loader: [{
test: /\.(jpe?g|png)$/I,
loaders: [
'file-loader',
'webp-loader?{quality: 13}'
]
}]
兼容性处理
目前 WebP 格式的图像并不适用于所有浏览器,所以在使用时,应当注意兼容处理好不支持的浏览器场景。
通常的处理思路分为两种:一种是在前端处理浏览器兼容性的判断,可以通过浏览器的全局属性 window.navigator.userAgent 获取版本信息,再根据兼容支持情况,选择是否请求 WebP 图像格式的资源;也可以使用 <picture> 标签来选择显示的图像格式,在 <picture> 标签中添加多个 <source> 标签元素,以及一个包含旧图像格式的 <img> 标签,当浏览器在解析DOM时便会对 <picture> 标签中包含的多个图片源依次进行检测。
倘若浏览器不支持 WebP 格式而未能检测获取到,最后也能够通过 <img> 标记兼容显示出旧图像格式,例如:
<picture>
<source srcset="/path/image.webp" type="image/webp" />
<img src="/path/image.jpg" alt="" />
</picture>
这里需要注意的是 <source> 标签的顺序位置,应当将包含 image/webp 的图像源写在旧图像格式的前面。
另一种是将判断浏览器是否支持的工作放在后端处理,让服务器根据 HTTP 请求头的 Accept 字段来决定返回图像的文件格式。如果 Accept 字段中包含 image/webp,就返回 WebP 图像格式,否则就使用旧图像格式(JPEG、PNG等)返回。这样做的好处是让系统的维护性更强,无论浏览器对 WebP 图像格式的兼容支持发生怎样的改变,只需要服务器检查 Accept 字段即可,无须前端跟进相应的修改。
2.5 SVG
矢量图
SVG 这种基于 XML 语法描述图像形状的文件格式,就适合用来表示 Logo 等图标图像,它可以无限放大并且不会失真,无论分辨率多高的屏幕,一个文件就可以统一适配;另外,作为文本文件,除了可以将 SVG 标签像写代码一样写在 HTML 中,还可以把对图标图像的描述信息写在以 .svg 为后缀的文件中进行存储和引用。
由于文本文件的高压缩比,最后得到的图像文件体积也会更小。要说缺点与不足,除了仅能表示矢量图,还有就是使用的学习成本和渲染成本比较高。
2.6 Base64
准确地说,Base64 并不是一种图像文件格式,而是一种用于传输 8 位字节码的编码方式,它通过将代表图像的编码直接写入HTML 或 CSS 中来实现图像的展示。一般展示图像的方法都是通过将图像文件的 URL 值传给 <img> 标签的 src 属性,当 HTML 解析到 <img> 标签时,便会发起对该图像 URL 的网络请求:
<img src="https://xx.cdn.com/photo.jpg">
当采用 Base64 编码图像时,写入 src 的属性值不是 URL 值,而是类似下面的编码:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACcAAAApCAYAAAC2qTBFAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABGSURBVFhH7c6hDQAwDMCw7v+jOz4YNGBZ4Zmzs2/fMFeZq8xV5ipzlbnKXGWuMleZq8xV5ipzlbnKXGWuMleZq8xV5pLZC2GjcwSKPE5fAAAAAElFTkSuQmCC
浏览器会自动解析该编码并展示出图像,而无须发起任何关于该图像的 URL 请求,这是 Base64 的优点,同时也隐含了对于使用的限制。由于 Base64 编码原理的特点,一般经过 Base64 编码后的图像大小,会膨胀四分之三。
这对想尝试通过 Base64 方式尽可能减少 HTTP 请求次数来说是得不偿失的,较复杂的大图经过编码后,所节省的几次 HTTP 请求,与图像文件大小增加所带来的带宽消耗相比简直是杯水车薪。所以也只有对小图而言,Base64 才能体现出真正的性能优势。
作为使用指导建议,建议在考虑是否使用 Base64 编码时,比对如下几个条件:
- 图像文件的实际尺寸是否很小。
- 图像文件是否真的无法以雪碧图的形式进行引入。
- 图像文件的更新频率是否很低,以避免在使用 Base64 时,增加不必要的维护成本。
格式选择建议
矢量图尽量使用SVG
位图尽量使用webP
动画用GIF
图像不需要更高细节JPEG
需要高清用png 色阶高png-24 色阶低用png-8
CSS Sprite
也就是我们常说的雪碧图,通过多张小图拼接成一张大图,有效的减少HTTP请求数量以达到加速显示内容的技术。
首先不建议将较大的图片拼接成雪碧图,因为大图拼接后体积会非常大,这样占用网络带宽的增加与请求完成所耗时间的延长,会完全淹没通过减少HTTP请求次数所带来的性能提升
雪碧图是通过background-position:0px 0px;来完成显示的
web字体
由于web字体视矢量图,所以不受屏幕尺寸和分辨率的影响,所以可以将项目中所用到的矢量图标打包到一个web字体文件中使用,以节省对图标资源的HTTP请求次数,类似于雪碧图
在web中由于存在兼容性问题,并没有哪一种字体可以适用于所以浏览器,所以在实际开发中需要提供多种文件格式来达到一致性
常见字体格式有EOT,TTF,WOFF,WOFF2
一般使用@font-face声明使用字体
@font-face{
font-family:'tianfont.';
src:url('//at.alicdn.com/t/font_1308833.eot');
src:url('//at.alicdn.com/t/font_1308833.eot'?iefix) format('embedded-opentype'),
url('//at.alicdn.com/t/font_1308833.woff2') format('woff2'),
url('//at.alicdn.com/t/font_1308833.woff') format('woff'),
url('//at.alicdn.com/t/font_1308833.ttf') format('truetype'),
}
1.字体文件预加载
在默认情况下,构建渲染树之前会阻塞字体文件的请求,这将导致部分文本渲染延迟.可以使用<link rel='preload'>
对字体资源预加载
<head>
<link rel='preload' href='/font/awesome.woff2' as='font'>
</head>
display:none的使用
如下代码
<div style='display:none'>
<img src='1.jpg'>
</div>
那么这个1.jpg会不会被浏览器发起请求,答案是会
<div class="parent" style="display:none; >
<div class="child" style="background:url(1.png)"></div>
<img src="pic2.png" />
</div>
<!-- pic1 没有发送请求,2 发送了请求 -->
<!-- 1、2都没有渲染 -->
设置了 display:none 的元素,其背景和后代元素的背景不会产生请求,但后代 <img> 元素会产生请求,以及这些图片都不会被渲染。