Web端的性能优化很多都集中在对JavaScript和CSS上面,比如"Move Scripts to the Bottom","Put Stylesheets at the Top"等等,很多时候,我们的开发中会忽略掉,图片的优化其实也是非常重要的一部分。
我们关注JS和CSS的重点也是如何能够更快地下载图片。图片是用户可以直观看到的,他们并不会关注JS和CSS。
以最近做的一个页面同花顺理财周为例:
xhr | img | css/js | doc | 其他 | |
---|---|---|---|---|---|
Size(kb) | 1.216 | 52.331 | 22 | 4.8 | 0 |
Time(ms) | 453 | 1628 | 822 | 164 | 0 |
web端的图片加载
在开始本文的介绍之前,先了解一些基础的知识:
在网页中插入一张图片可以使用html的<img>
标签[站外图片上传中……(2)]
,每插入新建一个<img>
标签就会新建一个Image 对象。
Image 对象的属性 onload 声明了一个事件句柄函数,当图像装载完毕的时候就会调用这个句柄。
Image 对象的属性 onerror 声明了一个事件句柄函数,当装载图像的过程中发生了错误时就会调用这个句柄。
我们目前在页面中采取的两种加载方式
针对首屏的banner图
<!-- html -->
[站外图片上传中……(2)]
<!-- js -->
function load_img(url, url_s, o) {
var img = new Image(); //创建一个Image对象,实现图片的预下载
img.src = url;
o.src = url_s;
if (img.complete) { //图片已经被缓存,直接替换
o.src = img.src;
return;
};
img.onload = function() { //图片下载完毕时替换
o.src = img.src;
};
};
load_img(("images/header.png"), ("images/header.jpg"), document.getElementById("img_init"));
原理:用一张很小的图片A替换清晰banner图B优先加载,然后js新建源为图B的Image对象并监控其加载进度,当图B加载完成之后,再替换掉图A。
不足:
- 功能单一,只能实现单张图片的预加载
- 只考虑了静态图片的加载,忽略了gif等动态图片,这些动态图片可能会多次触发onload
- 必须等待图片加载完毕, 仍然有优化空间
滚屏加载(图片的异步加载)
<!-- html -->
[站外图片上传中……(3)]
<!-- js -->
function timeOutLoad(container) {
this.container = container || document;
var imgs = [],_num = [];
var oriImgs = this.container.getElementsByClassName('timeOutLoad');
for (var i = 0; i < oriImgs.length; i++) {
var _data = oriImgs[i].getAttribute("data-original");
if (_data) {
_num.push(i);
imgs.push(_data);
}
}
function loadByOne(i) {
if (imgs.length > 0) {
_img = new Image();
_img.onload = function () {
oriImgs[_num[i]].setAttribute('src', _img.src);
oriImgs[_num[i]].removeAttribute('data-original');
loadByOne(i + 1);
};
_img.src = imgs.shift();
}
}
loadByOne(0);
}
timeOutLoad(document.getElementById("container"));
原理:通过判断当图片元素是否出现在视窗范围内后,则去加载图片资源,否则不加载。
延伸
特殊状态处理
- 加载中:在图片开始加载到触发onliad事件之前,展示加载的loading图
- 加载失败:文字提示,或错误提示图片替代
上报监控
- 加载失败触发onerror时,上报统计每天图片拉取失败的量
- 图片加载事件过长,上报分析cdn服务是否异常,或者是网速较慢的用户比例
webp
smaller、*richer *
一个较完善的可行方案
// 更新:
// 05.27: 1、保证回调执行顺序:error > ready > load;2、回调函数this指向img本身
// 04-02: 1、增加图片完全加载后的回调 2、提高性能
/**
* 图片头数据加载就绪事件 - 更快获取图片尺寸
* @version 2011.05.27
* @author TangBin
* @see http://www.planeart.cn/?p=1121
* @param {String} 图片路径
* @param {Function} 尺寸就绪
* @param {Function} 加载完毕 (可选)
* @param {Function} 加载错误 (可选)
* @example imgReady('http://www.google.com.hk/intl/zh-CN/images/logo_cn.png', function () {
alert('size ready: width=' + this.width + '; height=' + this.height);
});
*/
var imgReady = (function () {
var list = [], intervalId = null,
// 用来执行队列
tick = function () {
var i = 0;
for (; i < list.length; i++) {
list[i].end ? list.splice(i--, 1) : list[i]();
};
!list.length && stop();
},
// 停止所有定时器队列
stop = function () {
clearInterval(intervalId);
intervalId = null;
};
return function (url, ready, ,load error) {
var onready, width, height, newWidth, newHeight,
img = new Image();
img.src = url;
// 如果图片被缓存,则直接返回缓存数据
if (img.complete) {
ready.call(img);
load && load.call(img);
return;
};
width = img.width;
height = img.height;
// 加载错误后的事件
img.onerror = function () {
error && error.call(img);
onready.end = true;
img = img.onload = img.onerror = null;
};
// 图片尺寸就绪
onready = function () {
newWidth = img.width;
newHeight = img.height;
if (newWidth !== width || newHeight !== height ||
// 如果图片已经在其他地方加载可使用面积检测
newWidth * newHeight > 1024
) {
ready.call(img);
onready.end = true;
};
};
onready();
// 完全加载完毕的事件
img.onload = function () {
// onload在定时器时间差范围内可能比onready快
// 这里进行检查并保证onready优先执行
!onready.end && onready();
load && load.call(img);
// IE gif动画会循环执行onload,置空onload即可
img = img.onload = img.onerror = null;
};
// 加入队列中定期执行
if (!onready.end) {
list.push(onready);
// 无论何时只允许出现一个定时器,减少浏览器性能损耗
if (intervalId === null) intervalId = setInterval(tick, 40);
};
};
})();
调用案例:
imgReady('http://www.google.com.hk/intl/zh-CN/images/logo_cn.png', function (){
alert('size ready: width=' + this.width + '; height=' + this.height);
});