原文:https://www.zoo.team/article/picture
Web 页面性能优化,解决了图片相关,问题就解决了大半。本文从 Web 常见的图片格式入手,引出与图片优化相关的有效方案,期望对大家能有一点帮助。
“注:如下说明整理于网络公开信息。
更快、更简单、更稳定是我们每一个前端工程师的追求,HTTP/2 的出现让这些美好的词汇都汇聚在一起。首先来一个 demo 感受一下牛逼哄哄的 HTTP/2,HTTP/1.1 vs HTTP/2
HTTP/2 所有性能增强的核心在于新的二进制分帧层,它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。
HTTP/2 二进制分帧层
从这张图比较清晰地看出 HTTP/1.x 和 HTTP/2的区别。HTTP/1.x 协议以换行符作为纯文本的分隔符,而 HTTP/2 将所有传输的信息分割为更小的消息和帧,并采用二进制格式对它们编码。
一般情况下,客户端需要啥东西会告诉服务端,然后服务端返回对应的资源到客户端。HTTP/2 新增的另一个强大的新功能是,服务器可以对一个客户端请求发送多个响应。换句话说,服务端可以先于客户端检测到将要请求的资源,提前推送到客户端,不发送所有资源的实体,只发送资源的 URL。客户端接到后会进行验证缓存,如果发现需要这些资源,则正式发起请求。
每个 HTTP 传输都承载一组标头,这些标头说明了传输的资源及其属性。在 HTTP/1.x 中,这些元数据始终以纯文本形式,通常会给每个传输增加 500–800 字节的开销。如果使用 HTTP Cookie,增加的开销有时会达到上千字节。为了减少此开销和提升性能,HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:
在 HTTP/2 中,请求和响应标头字段的定义保持不变,仅有一些微小的差异:所有标头字段名称均为小写,请求行现在拆分成各个 :method
、:scheme
、:authority
和 :path
伪标头字段。
HPACK:HTTP/2 的标头压缩
每个 TCP 连接只能发送一个请求, HTTP/1.x 在前面的请求没有完成前,后面的请求将会阻塞。
其实在 HTTP/2 之前,我们在写代码的时候也用过类似的思想,比如
HTTP/2 Multiplexing
HTTP/2 的出现又可以让我们省掉不少麻烦。多路复用允许同时通过单一的 HTTP 请求多个响应。
只加载可视区的内容,当页面向下滚动时,再继续加载后面的内容。
图片懒加载的原理其实非常简单,我们先不设置图片的 src 属性,将图片的真实路径放到一个浏览器不认识的属性中(比如 data-src),然后我们去监听 scroll 事件。当页面的 scrollTop 与浏览器的高度之和大于图片距页面顶端的 Y (注意是整个页面不是浏览器窗口)时,说明图片已经进入可视区域,这是把 data-src 的值放到 src 中即可。
关于如何实现本文不做过多阐述,成熟的方案社区比比皆是。这边推荐几个比较好用的轮子。
安装
npm install --save lozad
使用
<img class="lozad" data-src="image.png" />
const observer = lozad(); // 默认会去找 .lozad 这个class
observer.observe();
安装
npm i vue-lazyload -S
使用
Vue.use(VueLazyload)
用高阶组件去包裹
安装
npm install --save react-lazyload
使用
import React from 'react';
import ReactDOM from 'react-dom';
import LazyLoad from 'react-lazyload';
import MyComponent from './MyComponent';
const App = () => {
return (
<div className="list">
<LazyLoad height={200}>
<img src="tiger.jpg" />
</LazyLoad>
<LazyLoad>
<MyComponent />
</LazyLoad>
</div>
);
};
ReactDOM.render(<App />, document.body);
缓存是一种保存资源副本并在下次请求时直接使用该副本的技术,因此使用 HTTP 缓存是 WEB 性能优化中必不可少的,也是每位前端开发工程师的必修课。
HTTP 请求
浏览器和服务器之间使用的缓存策略可以分为强缓存、协商缓存两种:
有这样一种场景,浏览器检查本地缓存找到之前响应的文件发现已经过期,只能去服务端请求,但是服务器的资源没有发生变化,可以说是浪费了一次请求。
Etag 的出现很好地解决了这个问题,其为一个哈希值,浏览器甚至不用去关系这个值是怎么来的,在第一次请求时,浏览器生成 Etag 并发送到服务端。浏览器下一次请求时发现这个值未变,就跳过请求。
HTTP Cache-Control 示例
浏览器自动在 If-None-Match
HTTP 请求头内提供 ETag。服务器根据当前资源核对令牌,如果它未发生变化,服务器将返回 304 Not Modified
响应。这样一个来回避免了浏览器再次去请求资源,即省钱又省时间。
Cache-Control 是强缓存的一种,每个资源都可通过 Cache-Control 定义其缓存策略,Cache-Control 来控制谁可以缓存、缓存多久。
无需和服务端通信的请求是最佳的,通过本地副本消除所有网络延迟、节省流量。
Cache-Control 是在 HTTP/1.1 规范中定义的,取代了之前用来定义响应缓存策略的头部(例如 Expires)。所有现代浏览器都支持 Cache-Control,因此,用他就足够了。
HTTP Cache-Control 示例
指令 | 说明 |
---|---|
max-age | 指令指定从请求的时间开始,允许提取的响应被重用的最长时间(单位:秒) |
private | 只为单个用户缓存,不允许任何中间缓存对其进行缓存 |
no-cache | 先与服务器确认返回的响应是否发生了变化,走协商缓存 |
no-store | 禁止浏览器以及所有中间缓存存储任何版本的返回响应 |
实际开发中往往不存在什么固定的最优解,我们需要根据不同的业务场景制定相应的策略。
雪碧图,CSS Sprites,国内也叫 CSS 精灵,是一种 CSS 图像合成技术,主要用于小图片显示。
在网页中,为了更好的展现效果,经常会采用一些小图标来替代文字。常用的方式包括 Icon Fonts、SVG Icons、小图片,其中 Icon Fonts 只支持单色,SVG Icons 需 IE9+。
“注:如不考虑低版本浏览器兼容性,推荐使用 SVG Icons,有兴趣可以阅读 张鑫旭-SVG Sprites技术介绍
如果采用小图片,需要考虑一个问题:每张小图片独立请求,加载时都会产生一个 HTTP 请求,而小图片体积(1 ~ 2 kb)就比较小,对比上 HTTP 请求连接、请求头内容、响应等的开销,就显得非常没必要了,那有没有可能将多张小图片合并成一张图?
将小图标合并成一张图片,利用 backround-position
属性值来确定图片呈现的位置即可。如下图所示不同尺寸、位置:
图片
通过 CSS 定位,可以展现对应的图标。
.icon-alipay {
background-image: url(sprite.png);
background-position: 0px -131px;
width: 81px;
height: 73px;
}
.icon-taobao {
background-image: url(sprite.png);
background-position: -177px 0px;
width: 114px;
height: 114px;
}
.icon-wechat {
background-image: url(sprite.png);
background-position: 0px 0px;
width: 177px;
height: 131px;
}
.icon-xinlang {
background-image: url(sprite.png);
background-position: -81px -131px;
width: 72px;
height: 72px;
}
每次修改或者新增图片时,都需要对雪碧图进行修改,如果依靠人工维护,成本太高,能否自动生成雪碧图和样式呢?可以使用 spritesmith,该工具可自动合并图片,并得到图片在合并之后的相对位置。简单使用示例代码如下:
const fs = require('fs')
const path = require('path');
const Spritesmith = require('spritesmith');
const baseDir = './images';
const files = fs.readdirSync(baseDir)
const sprites = files.map(file => path.join(baseDir, file))
Spritesmith.run({src: sprites}, (err, result) => {
if (err) {
console.error(err)
} else {
console.info(result);
}
})
运行的输出结果如下:
{
coordinates: {
'images/alipay.png': { x: 0, y: 131, width: 81, height: 73 },
'images/taobao.png': { x: 177, y: 0, width: 114, height: 114 },
'images/wechat.png': { x: 0, y: 0, width: 177, height: 131 },
'images/xinlang.png': { x: 81, y: 131, width: 72, height: 72 }
},
properties: { width: 291, height: 204 },
image: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 01 23 00 00 00 cc 08 06 00 00 00 38 45 c5 ce 00 00 40 06 49 44 41 54 78 01 ec c1 0b 98 e6 05 61 ... 22705 more bytes>
}
其中
coordinates
:每张图片对应的尺寸和位置properties
:生成的图片尺寸image
:文件的 Buffer,可用于生成图片对于现有的常用的构建工具,已有现成的插件可直接使用:
以下为 gulp
配合 gulp.spritesmith
的示例代码:
const gulp = require('gulp');
const spritesmith = require('gulp.spritesmith');
// gulp任务定义
gulp.task('sprite', () => {
return gulp.src('images/*.png')
.pipe(spritesmith({
imgName: 'sprite.png',
cssName: 'sprite.css'
})
).pipe(gulp.dest('./'));
});
运行后会得到sprite.png
、sprite.css
两个文件,最后打包的时候将文件对应打包进去即可。
background-size
属性来使得最终显示正常,可参考以上插件的retina
相关配置参数;iconfont 译为字体图标,即通过字体的方式展示图标,多用于渲染图标、简单图形、特殊字体等。
使用 iconfont 时,由于只需要引入对应的字体文件,针对加载图片张数较多的情况,可有效减少 HTTP 请求次数,而且一般字体体积较小,所以请求传输数据量较少。与直接引入图片不同,iconfont 可以像使用字体一样,设置大小和颜色,还可以通过 CSS 设置特殊样式,且因为其是矢量图,不存在失真的情况。
那么,怎么使用 iconfont 呢?请参照以下 demo:
“根据开发需求,按需引入不同格式的字体文件(eot / ttf / woff / svg)
<style>
@font-face {
font-family: "iconfont";
src: url('iconfont.eot'); /* IE9*/
src: url('iconfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('iconfont.woff') format('woff'),
url('iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('iconfont.svg?#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont";
}
</style>
<body>
//  是一个字符的 unicode 码,在该 iconfont 字体文件中对应某个图标
<i class="iconfont"></i>
</body>
关于 @font-face 的说明可参考 mozilla 官方文档。
在平时开发工作中,可使用以下常用图标字体库:
Base64 是网络上最常见的用于传输 8Bit 字节码的编码方式之一,可将图片编码为特定的字符串,由 52 个大小字母和 10 个数字,以及 +、/、= 三个字符组成,详见 wiki.
使用 Base64 编码渲染图片有以下优点:
凡事皆有利弊,使用 Base64 编码同时也会带来一些问题:
如需将图片转换为 Base64 编码,可以使用 JavaScript API FileReader.readAsDataURL
,详细可参考文档。
图片体积压缩就是在图片保持在可接受的清晰度范围内同时减少文件大小,图片体积压缩可以借助工具实现。
阿里云 OSS 可以通过配置参数形式对图片进行处理,支持缩放、裁剪、旋转、效果、格式转换、水印等操作,详细信息点此文档 (https://help.aliyun.com/document_detail/44688.html) 查看。
示例:将图强制缩略成宽度为 100,高度为 100。
http://image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg?x-oss-process=image/resize,m_fixed,h_100,w_100
本文作者之一 @明明 亦做过一个小工具,通过配置缩放参数、压缩质量、格式等属性后自动生成 OSS 后缀地址,具体如何使用参考文档 (https://npm.taobao.org/package/vue-img-url-suffix-for-alioss)。
七牛云的图片处理服务和阿里云 OSS 功能类似,也可以对图片进行缩放、裁剪、旋转、缩略等操作,详细文档 (https://developer.qiniu.com/dora/api/3683/img-directions-for-use)
示例:将图强制缩略成宽度为 100,高度为 100。
https://odum9helk.qnssl.com/resource/Ship.jpg?imageView2/2/w/100/h/100
Web 开发中常见的图片包括 JPG,PNG,GIF,webP,选择合适的格式以及压缩质量可以在保证视觉效果的情况下,加速网站的呈现。下面针对不同图片格式的特性来做一下对比:
类型 | 动画 | 压缩类型 | 浏览器支持 | 透明度 |
---|---|---|---|---|
GIF | 支持 | 无损压缩 | 所有 | 支持 |
PNG | 不支持 | 无损压缩 | 所有 | 支持 |
JPEG | 不支持 | 有损压缩 | 所有 | 不支持 |
webP | 不支持 | 无损压缩或有损压缩 | Chrome、Opera、Firefox、Edge、Android | 支持 |
回忆本文开头介绍的不同图片格式的特点,大家可以参考下图选择合适的使用场景:
image
在 Retina 视网膜屏幕面世之前人们很少关注像素密度与设备像素比,随着 Retina 屏在移动设备中越来越广泛地应用,为了保证图片在不同 DPR(设备像素比)的设备上显示足够清晰,开发者需要针对不同设备适配不同倍数的图片。
window.devicePixelRatio
来获取。了解以上的概念,我们不难理解:
针对一张 30px * 30px 的图片,在 1 倍屏上,按照 1 : 1 平铺,图片质量并不损失。但是在 2 倍屏、3 倍屏上,分别通过 60 * 60 与 90 * 90 个物理像素渲染这张图片就会出现模糊、失真的现象,从而影响用户体验。所以,我们需要根据不同 DPR 去加载不同倍数的图片:
在高分辨率显示屏如 2x 上,在页面中使用二倍图可以保证清晰度,但是当此页面在低 DPR 设备打开时,我们只需要 50% 长宽的图片就能保证显示效果,而此时带宽开销却是一样的。所以为了节约传输流量,我们需要告诉浏览器,根于不同的 DPR 加载不同尺寸的图片,通常有以下三种方法:
<picture>
<source srcset="photo@3x.jpg" media="(min-width: 800px)">
<source srcset="photo@2x.jpg" media="(min-width: 600px)">
<img srcset="photo.jpg">
</picture>
<img src="photo.png" srcset="photo@2x.png 2x, photo@3x.png 3x" alt="photo" />
background-image: image-set("photo.png" 1x,
"photo@2x.png" 2x,
"photo@3x.png" 3x);
根据世界健康组织的统计,全球约有 2.85 亿视力障碍人士,仅美国就有 810 万网民患视力障碍,而在中国,这个数字要被乘以 2。不同于我们浏览网页的方式是看,视力障碍人员浏览互联网信息主要是靠听 —— 靠屏幕阅读器读出网页的有效信息,通过听这些信息来获知内容。对于前端工程师而言,关注到一些细节的优化,能更好的服务于这些视力障碍人士。
最基础的方式,是装饰性图片归类到背景图,通过 CSS 背景图进行设置;功能性图片放到 HTML 中,通过 img
标签引入,且要设置 alt
属性,以便被屏幕阅读器识别并阅读。图片 alt
信息应简短,介绍图片信息即可,避免内容冗余。图片的长信息介绍应被放到 longdesc
属性中:
<img src="" alt="图片说明" />
<img src="" longdesc="一段很长的文字一段很长的文字一段很长的文字一段很长的文字" />
更多无障碍相关,可参考《腾讯网无障碍说明》。
“高对比度模式” 是一种 Windows 系统的设置主题,其用意是为了保证视力受损的用户,在查看 Web 信息时提供方便。它通过使用对比鲜明的色彩和字号来提高文本的可读性,高对比度模式下网页的背景默认会变成全黑。
CSS Image Sprites(CSS 雪碧图)是一项用来减少网页中图片 HTTP 请求数的技术,但其会导致在 Windows 高对比度模式下背景图片消失,其服务的 Web 应用性能的提升和对无障碍体验被破坏之间的矛盾,已经引起了 Web 开发者的关注。Sprite 技术的使用的确为更多网站的优化加载速度的体验贡献甚大,但我们要承认,这个过程中我们忽略并损害了使用高对比度模式用户的体验。由于 <img>
元素可以在高对比度模式下显示,故而在此场景下,使用基于 <img>
标签的 Sprite 技术,可以得到比基于 CSS 背景图的 Sprite 更多的收益。
关于 IMG Sprite 技术的应用,可以在此文中学习到 《Foreground Sprites – High Contrast Mode Optimization》
就前端性能优化而言,图片优化可谓是其必不可少的环节。但是与其说是在做优化,不如说是在做“权衡”。一些操作是以牺牲一部分成像质量为代价的。我们的主要任务,是尽可能的去寻求一个质量与性能之间的平衡点,并在不同业务场景下,做好图片方案的选型工作。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有