性能优化与图片
总览
图片格式、图片压缩、常见的图片性能优化手段等
# 设备独立像素DIP与device-pixel-ratio
设备独立像素(DIP)
独立于设备的用于逻辑上衡量长度的单位,由底层系统的程序使用,会由相关系统转换为物理像素。所以它只是一个虚拟像素单位
物理像素和设备独立像素( device-independent pixels (dips) )的比例
即 devicePixelRatio = 屏幕物理像素/设备独立像素
举个例子,iPhone 3GS 和 iPhone 4/4s 的尺寸都是 3.5 寸,但 iPhone 3GS 的分辨率是 320x480,iPhone 4/4s 的分辨率是 640x960,这也就是意味着同样长度的屏幕,iPhone 3GS 有 320 个物理像素,iPhone 4/4s 有 640 个物理像素。
我们统一 iPhone 3GS 和 iPhone 4/4s 都是 320 个虚拟像素,只是在 iPhone 3GS 上,最终 1 个虚拟像素换算成 1 个物理像素,在 [iphone 4s](https://www.zhihu.com/search?q=iphone 4s&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A541637971}) 中,1 个虚拟像素最终换算成 2 个物理像素。
至于 1 个虚拟像素被换算成几个物理像素,这个数值我们称之为设备像素比。
我们主要通过:通过判断 devicePixelRatio 的值来加载不同尺寸的图片
针对普通显示屏(devicePixelRatio = 1.0、1.3),加载一张 1 倍的图片
针对高清显示屏(devicePixelRatio >= 1.5、2.0、3.0),加载一张 2 倍大的图片
# JPEG
JPEG(Joint Photographic Experts Group)联合图像专家组 。是 JPEG 标准的产物,该标准由国际标准化组织(ISO)制订,是面向连续色调静止图像的一种压缩标准。 JPEG 格式是最常用的图像文件格式后缀名为 .jpg 或 .jpeg 。是一种针对彩色照片的有损压缩图形格式,压缩率高。
优势
- 它支持极高的压缩率,因此JPEG图像的下载速度大大加快。
- 它能够轻松地处理16.8M颜色,可以很好地再现全彩色的图像。
- 在对图像的压缩处理过程中,该图像格式可以允许自由地在最小文件尺寸(最低图像质量)和最大文件尺寸(最高图像质量)之间选择。
- 该格式的文件尺寸相对较小,下载速度快,有利于在带宽并不“富裕”的情况下传输。
劣势
- 并非所有的浏览器都支持将各种JPEG图像插入网页。
- 压缩时,可能使图像的质量受到损失,因此不适宜用该格式来显示高清晰度的图像。
适合使用的场景:颜色丰富的照片
不适合使用的场景:线条图形和文字、图标图形,因为它的压缩算法不太适合这些类型的图形;并且不支持透明度。
# PNG
png (Portable Network Graphics) 便携式网络图形,是一种采用无损压缩算法的位图格式,其设计目的是试图替代GIF (opens new window)和TIFF (opens new window)文件格式,同时增加一些GIF文件格式 (opens new window)所不具备的特性。
PNG格式有8位、24位、32位三种形式,其中8位PNG支持两种不同的透明形式(索引透明和alpha透明),24位PNG不支持透明,32位PNG在24位基础上增加了8位透明通道,因此可展现256级透明程度。
PNG8和PNG24后面的数字则是代表这种PNG格式最多可以索引和存储的颜色值。8代表2的8次方也就是256色,而24则代表2的24次方大概有1600多万色。
优势
- 无损压缩
- 完全支持 alpha 透明度。
- 可以重复保存且不降低图像质量
劣势
- 体积偏大
适合的场景:纯色、透明、线条绘图、图标、边缘清晰,有大块相同颜色区域。颜色数较少,但是需要半透明。
不适合的场景:由于无损存储,图片体积较大,不适合需要图片特别大和彩色图像的场景。
# SVG
SVG Scalable Vector Graphics,意思为可缩放的矢量图形 (opens new window)。严格来说应该是一种开放标准的矢量图形语言,可让你设计高分辨率的Web图形页面。用户可以直接用代码来描绘图像,可以用任何文字处理工具打开SVG图像,通过改变部分代码来使图像具有交互功能,并可以随时插入到HTML中通过浏览器来观看。
优势
- SVG 可被非常多的工具读取和修改
- SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强
- SVG 可在图像质量不下降的情况下被放大
劣势
- 不是所有的浏览器都支持SVG,IE8和早期版本都需要一个插件
- 复杂的图片会降低渲染速度(只支持小图)
IE8和早期版本的 ie 浏览器,如果想在页面中展现 svg 图片,需要安装 adobe 的 SVGView 插件。
适合:高分辨率打印图片程序、设计动画、演示等...
不适合:对渲染速度要求比较大的网站,不适合需要图片特别大的场景。
# 使用 iconfont
iconfont(字体图标),即通过字体的方式展示图标,多用于渲染图标、简单图形、特殊字体等。
- 像使用字体一样,设置大小、颜色及其他样式,不失真
- 轻量,易修改
- 有效减少 HTTP 请求次数
# 使用 CDN 图片
CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术。
基本原理
CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。
基本思路
CND 的基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN 系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet 网络拥挤的状况,提高用户访问网站的响应速度。
CDN 的优势
- CDN 节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;
- 大部分请求在 CDN 边缘节点完成,CDN 起到了分流作用,减轻了源站的负载。
# 图片懒加载
懒加载是一种网页性能优化的方式,它能极大的提升用户体验。图片一直是影响网页性能的主要元凶,现在一张图片超过几兆已经是很经常的事了。如果每次进入页面就请求所有的图片资源,那么可能等图片加载出来用户也早就走了。所以进入页面的时候,只请求可视区域的图片资源。
总结出来就是:
- 减少资源的加载,页面启动只加载首屏的图片,这样能明显减少了服务器的压力和流量,也能够减小浏览器的负担。
- 防止并发加载的资源过多而阻塞 js 的加载,影响整个网站的启动,影响用户体验
- 浪费用户的流量,有些用户并不想全部看完,全部加载会耗费大量流量。
原理
图片懒加载的原理就是暂时不设置图片的 src 属性,而是将图片的 url 隐藏起来,比如先写在 data-src 里面,等当前图片是否到了可视区域再将图片真实的 url 放进 src 属性里面,从而实现图片的延迟加载。
function lazyload() {
let viewHeight = document.body.clientHeight //获取可视区高度
let imgs = document.querySelectorAll('img[data-src]')
imgs.forEach((item, index) => {
if (item.dataset.src === '') return
// 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
let rect = item.getBoundingClientRect()
if (rect.bottom >= 0 && rect.top < viewHeight) {
item.src = item.dataset.src
item.removeAttribute('data-src')
}
})
}
// 可以使用节流优化一下
window.addEventListener('scroll', lazyload)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
通过上面例子的实现,我们要实现懒加载都需要去监听 scroll 事件,尽管我们可以通过函数节流的方式来阻止高频率的执行函数,但是我们还是需要去计算 scrollTop,offsetHeight 等属性,有没有简单的不需要计算这些属性的方式呢,答案是有的---IntersectionObserver
const imgs = document.querySelectorAll('img[data-src]')
const config = {
rootMargin: '0px',
threshold: 0,
}
let observer = new IntersectionObserver((entries, self) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
let img = entry.target
let src = img.dataset.src
if (src) {
img.src = src
img.removeAttribute('data-src')
}
// 解除观察
self.unobserve(entry.target)
}
})
}, config)
imgs.forEach((image) => {
observer.observe(image)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 图片预加载
图片预加载,是指在一些需要展示大量图片的网站,将图片提前加载到本地缓存中,从而提升用户体验。
常用的方式有两种,一种是隐藏在 css 的 background 的 url 属性里面,一种是通过 javascript 的 Image 对象设置实例对象的 src 属性实现图片的预加载。
1、用 CSS 和 JavaScript 实现预加载
#preload-01 {
background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px;
}
#preload-02 {
background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px;
}
#preload-03 {
background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px;
}
2
3
4
5
6
7
8
9
通过 CSS 的 background 属性将图片预加载到屏幕外的背景上。当它们在 web 页面的其他地方被调用时,浏览器就会在渲染过程中使用预加载(缓存)的图片。该方法虽然高效,但仍有改进余地。使用该法加载的图片会同页面的其他内容一起加载,增加了页面的整体加载时间。 为了解决这个问题,我们增加了一些 JavaScript 代码,来推迟预加载的时间,直到页面加载完毕。
function preloader() {
if (document.getElementById) {
document.getElementById('preload-01').style.background =
'url(http://domain.tld/image-01.png) no-repeat -9999px -9999px'
document.getElementById('preload-02').style.background =
'url(http://domain.tld/image-02.png) no-repeat -9999px -9999px'
document.getElementById('preload-03').style.background =
'url(http://domain.tld/image-03.png) no-repeat -9999px -9999px'
}
}
function addLoadEvent(func) {
var oldonload = window.onload
if (typeof window.onload != 'function') {
window.onload = func
} else {
window.onload = function () {
if (oldonload) {
oldonload()
}
func()
}
}
}
addLoadEvent(preloader)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2、使用 JavaScript 实现预加载
function preloader() {
if (document.images) {
var img1 = new Image()
var img2 = new Image()
var img3 = new Image()
img1.src = 'http://domain.tld/path/to/image-001.gif'
img2.src = 'http://domain.tld/path/to/image-002.gif'
img3.src = 'http://domain.tld/path/to/image-003.gif'
}
}
function addLoadEvent(func) {
var oldonload = window.onload
if (typeof window.onload != 'function') {
window.onload = func
} else {
window.onload = function () {
if (oldonload) {
oldonload()
}
func()
}
}
}
addLoadEvent(preloader)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 响应式图片
根据用户的设备和使用场景提供合适的图片,在不同的网络环境下,加载不同尺寸和像素的图片。一般都是通过图片 URL 的参数来改变图片的尺寸。
根据用户的设备和使用场景提供合适的图片。对于开发者来说不可能预见用户使用的浏览器的所有设置,只有浏览器在打开和渲染的时候才会知道它设备的具体情况(屏幕大小、能力、尺寸等)。针对这些请求,开发者一般会准备不同版本的图片,例如:大、中、小,不同版本的图片对于不同屏幕大小和分辨率。选择最适合的视图尺寸往往能减少图片的大小,让图片加载的更快。
srcset 切换分辨率
<img
src="纸箱回收.png"
srcset="金属类.png 1.1x, 布料类.png 1.2x"
/>
2
3
4
注意: 在 Chrome 中测试时,通过如下方式禁用缓存:打开 DevTools ,并选中 Settings > Preferences > Network下Disable cache的选择框。否则,Chrome 会优先选择缓存图片而不是恰好适配的那个。
src 属性是我们非常熟悉的一个属性,在这里有两个作用:
- 指定 1x 大小的图片
- 在浏览器不支持 srcset 属性时的后备方案
srcset 属性(浏览器支持的情况下),通过逗号分割图片的描述,让浏览器自己决定使用哪一个图片。
但是不幸的是,这种写法会带来一些问题,例如 375像素 1x 、900像素 1.25x 都是使用的相同的图片,这其实并不是我们想要的结果。
srcset & sizes 联合切换
srcset="uswnt-480.png 480w,
uswnt-640.png 640w,
uswnt-960.png 960w,
uswnt-1280.png 1280w"
sizes="(max-width: 480px) 50vw,
(max-width: 640px) 25vw,
(max-width: 960px) 50vw,
(max-width: 1280px) 50vw,
640px"
src="uswnt-640.png" alt="" />
2
3
4
5
6
7
8
9
10
利用srcset和sizes信息来选择最符合规定条件的图像。
我测试电脑的 dpr = 2;
- 如果浏览器的 viewport 为小于等于 480 像素,图片宽高为 50%(宽高 = 480 * 0.5),浏览器视图加载一张 480 * 0.5 * dpr(2) = 480像素的图片,uswnt-480.png。
如果浏览器的 viewport 为小于等于 640 像素,图片宽高为 25%(宽高 = 640 * 0.25),浏览器视图加载一张 640 * 0.25* dpr(2) = 320 像素的图片,浏览器会尝试加载一张靠近尺寸的图片 uswnt-480.png(如图一所示)。
- 如果我这里将 25 vw 修改为 40 vw,640 * 0.4* dpr(2) = 512,相对 640 和 480 更加接近 480 所以会显示 uswnt-480.png(如图二所示)。
- 如果我这里将 25 vw 修改为 45 vw,640 * 0.45* dpr(2) = 576,相对 640 和 480 更加接近 640 所以会显示 uswnt-640.png(如图三所示)。
图一
图二
图三
注意:sizes 属性仅仅是对浏览器给出提示,并不能保证浏览器就会言听计从。所以我们无法确定究竟显示哪张图像,因为每个浏览器根据我们提供的信息挑选适当图像的算法是有差异的。srcset 和 size 列表是对浏览器的一个建 议(hint),而非指令。例如,设备像素比(dpr)为1.5的设备,亦可用1x也可用2x的图像,由浏览器根据其能力、网络等因素来决定
picture 元素
<picture>
<source srcset="/media/cc0-images/surfer-240-200.jpg"
media="(min-width: 800px)">
<img src="/media/cc0-images/painted-hand-298-332.jpg" alt="" />
</picture>
2
3
4
5
要决定加载哪个 URL,user agent (opens new window) 检查每个 的 srcset (opens new window)、media (opens new window) 和 type (opens new window) 属性,来选择最匹配页面当前布局、显示设备特征等的兼容图像。
media 属性
media 属性允许你提供一个用于给用户代理作为选择 元素的依据的媒体条件 (media condition)(类似于媒体查询)。如果这个媒体条件匹配结果为 false,那么这个 元素会被跳过。
<picture>
<source srcset="1.png" media="(min-width: 600px)">
<img src="2.png" alt="MDN">
</picture>
2
3
4
type 属性
type 属性允许你为 元素的 srcset 属性指向的资源指定一个 MIME 类型 (opens new window)。如果用户代理不支持指定的类型,那么这个 元素会被跳过。
<picture>
<source srcset="1.svg" type="image/svg+xml">
<img src="1.png" alt="MDN">
</picture>
2
3
4
不建议使用 CSS 或 JavaScript 来做响应式图片的效果
当浏览器开始加载一个页面, 它会在主解析器开始加载和解析页面的 CSS 和 JavaScript 之前先下载 (预加载) 任意的图片。这是一个非常有用的技巧,平均下来减少了页面加载时间的20%。但是, 这对响应式图片一点帮助都没有, 所以需要类似 srcset的实现方法。因为你不能先加载好元素后, 再用 JavaScript 检测可视窗口的宽度,如果觉得大小不合适,再动态地加载小的图片替换已经加载好的图片,这样的话, 原始的图像已经被加载了, 然后你又加载了小的图像, 这样的做法对于响应式图像的理念来说,是很糟糕的。
# 图片逐渐加载
统一占位符
在图片还处于加载中时,通常需要填充页面预览效果,这时往往会用到图片占位符,例如:天猫、京东使用一个 div 来充当图片加载中的占位。
我们也可以使用 js 的手段,动态的修改 img 的 src 达到占位的效果。
xml复制代码 <style>
div {
height: 1024px;
width: 1024px;
}
img {
position: relative;
height: 100%;
width: 100%;
}
img::after {
content: "";
height: 100%;
width: 100%;
position: absolute;
background: url(1.png) no-repeat center;
}
</style>
<body>
<div>
<img src="">
</div>
</body>
<script>
setTimeout(function() {
document.querySelectorAll("img")[0].src = '2.png';
}, 3000);
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
LQIP: Low Quality Image Placeholders
根据原始图片自动生成超小尺寸的缩略图。
const lqip = require('lqip');
const file = `./dest/to/file/zouhir-riding-a-bike.jpg`;
lqip.base64(file).then(res => {
console.log(res); // "data:image/jpeg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhY.....
});
2
3
4
5
6
7
注意:lqip 只支持 'jpeg', 'jpg', 'png'
利用生成的 base64 来填充 src,但是我们在实际的项目中肯定不能手动的为每一张图片都用 lqip 生成一个 base64 填充到 img 中,这样效率也太慢了。所以工程化的角度有一个 lqip-loader 为JS 包中导入的 jpeg 图像生成 Base64。
lqip-loader (opens new window)
并且针对 Base64,在 Chrome 为了节省内存和提高 GPU 性能,浏览器(包括从 61.0.3163.38 开始的 Chrome)现在将呈现稍微更清晰或像素化的 Base64 编码图像。
左侧是旧版 Chrome,右侧是 Chrome v61
SQIP:SVG-based LQIP technique
SQIP 将生成基于 SVG 的图像预览。它们可以用作延迟加载图像预览、视频缩略图或项目的艺术元素。
# 图片替代
Data URI
Data URI其实就是将图片数据转成base64字符串格式,再引入,相比普通的引入方式它不再需要发起网络请求,从而达到优化网页快速加载的目的(减少http请求)。
Data URI适用的场景
- 图片体积较小,没必要浪费一次http请求
- 访问外部资源受限或者很麻烦的情况
- 外部图片为动态的
Data URI引入方式的一些缺点
- Base64编码的数据体积是原始二进制图片的4/3,也就是大1/3
- 虽然相比普通引入加载更快,但是渲染更加耗时。实验结果显示 Data URI 的渲染比普通图片多消耗 53% 左右的CPU资源,内存多出4倍左右,耗时平均高出24.6倍,所以要在可接受的范围内适量使用
- 不会被浏览器缓存(可以通过css引入的方式来缓存)
# 服务器端优化图片
图片服务器自动优化是可以在图片 URL 链接上增加不同参数,服务器自动生成不同格式、大小、质量的一种技术。例如在前面提到的「图片尺寸随网络环境变化」通过修改 URL 参数来达到目的也就是这样一个例子。
# 常见的服务端处理功能
- 图片裁剪:按照长度、宽度、固定大小、长边、短边、填充、拉伸等缩放
- 图片格式装换:支持不同图片格式 jpg、gif、png、webp,支持不同的图片压缩率
- 图片处理:添加图片水印、模糊等
- AI 处理:图片鉴别、抠图、排版、配色、合成等
# 服务端优化实现技术
- 将图片压缩、裁剪、格式转换等本地攻击部署到线上图片服务器上
- 内部运营或外网用户上传本地图片到图片服务知乎,服务器默认处理转换为多种格式,并推送到图片 CDN 服务器上
- 图片服务器对外开放多个域名,同事对各个业务线开发不同的业务路径
- 外网永华请求带特殊参数的图片 URL 时,图片服务器根据 URL 种不同的参数类型,从本地缓存中存取,或者实现对图片进行即时处理,返回给客服端
# 参考
图片优化不完全指北 - 掘金 (juejin.cn) (opens new window)
什么叫物理像素值,什么叫设备独立像素值(dips)? - 知乎 (zhihu.com) (opens new window)