1. 问题

elitemba主站 以图片展示为主,启动页面时加载过多图片,图片体积过大,导致等待时间过长。这些图片请求几乎是并发的。在Chrome浏览器,同一域名,最多支持6个请求的并发,其他的请求将会推入到队列中等待,直到6个请求中的一个完成后,队列中的新请求才会发出。

下面列出一些图片加载优化方法。

1. 压缩图片体积

1.1 图片格式

Web图片格式有JPEG/JPG、PNG、WebP、Base64、SVG等。

JPEG/JPG
有损压缩、体积小、不支持透明。当我们把图片体积压缩至原有体积的 50% 以下时,JPG 仍然可以保持住 60% 的品质。此外,JPG 格式以 24 位存储单个图,可以呈现多达 1600 万种颜色。JPG 适用于呈现色彩丰富的图片,在我们日常开发中,JPG 图片经常作为大的背景图、轮播图或 Banner 图出现。

但它处理矢量图形和logo等线条感较强、颜色对比强烈的图像时,人为压缩导致的图片模糊会相当明显。

PNG-8/PNG-24
无损压缩、质量高、体积大、支持透明 PNG是一种无损压缩的高保真的图片格式,8位PNG支持256中颜色,24位可呈现1600万中颜色。考虑到 PNG 在处理线条和颜色对比度方面的优势,我们主要用它来呈现小的 Logo、颜色简单且对比强烈的图片或背景。

SVG
文本文件、体积小、不失真、兼容性好SVG是一种基于XML语法的图像格式,它对图像的处理不是基于像素点,而是基于对图像的形状描述。它的优势是图片可无线放大而不失真。因为SVG是文本文件,我们可以像写代码一样定义SVG,把它写在HTML里、成为DOM的一部分。

Base64
Base64不是一种图片格式,而是一种编码方式,作为小图标解决方案而存在。通过对图片进行baser64编码,我们可以直接将编码结果写入HTML或者CSS,从而减少HTTP请求的次数。

WebP
它于 2010 年被提出, 是Google专为Web开发的一种旨在加快图片加载速度的图片格式,它支持有损压缩和无损压缩。与png相比,WebP无损图像的大小可以缩小26%,比同类jpeg小25-36%。

1.2 图片裁剪

  • 阿里云oss图片服务 / 七牛图片服务,提供了图片格式转换、按尺寸裁剪等图片处理功能。配合lib-flexible,就可以对不同设备加载不同尺寸的图片。

2. 占位显示

1
2
3
4
5
6
7
8
9
10
11
12
13
img{
position: relative;
}
img::after{
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
padding-top: 60%; /* 图片比例 */
background: #ccc no-repeat center center;
}
<img src="1.png">

pc上,一般我们会为<img>设置width和height属性来解决图片占位问题。但在移动端,用占位图或min-height来占位,如果与需要加载的图片尺寸差别很大,会出现页面的内容跳动,这也不是我们想要的。

3. 懒加载

滚动到可视区域后再去加载图片

3.1 原理

将页面中<img>src指向一张默认图,然后定义data-src指向真实的图片。

1
2
<!-- 注意: 图片要指定宽高 -->
<img src="default.jpg" data-src="http://www.elitemba.cn/static/images/1.png">

当载入页面时,先把可视区域内的<img>标签的data-src属性值赋给src。然后监听滚动事件,同理修改可视区域的图片的src值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var img = document.getElementsByTagName('img');
var num = img.length;
var n = 0; // 存储图片加载到的位置,避免每次都从第一张图片开始遍历

lazyload(); // 页面载入完毕加载可视区域内的图片
window.onscroll = lazyload;

function lazyload(){
var seeHeight = document.documentElement.clientHeight; // 可见区域高度
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; // 滚动条距离顶部高度
for ( var i = n ; i < num; i++ ){
if (img[i].offsetTop < seeHeight + scrollTop){
if (img[i].getAttribute("src") == "default.jpg"){
img[i].src = img[i].getAttribute("data-src");
}
n = i + 1;
}
}
}

3.2 节流函数

当函数绑定在scroll事件上,页面滚动时,函数会被高频触发,这非常影响浏览器的性能。这时需要限制触发频率。
节流函数: 只允许一个函数在n秒内执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// fun 要执行的函数
// time 在time时间内必须执行一次
function throttle(fun, time){
var startTime = new Date();

return function(){
var context = this,
args = arguments,
curTime = new Date();

// 如果达到了规定的触发时间间隔,触发handler
if ( curTime - startTime >= time ){
fun.apply(context, args);
startTime = curTime;
}
}
}

window.addEventListener("scroll", throttle(lazy, 1000))

3.3 去抖函数

让函数延迟执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function debounce(fn, delay){
let time = null;

return function(){
let context = this;
let args = Array.prototype.slice.call(arguments);
clearTimeout(time);
timer = setTimeout(function(){
fn.apply(context, args)
}, delay)
}
}

// 让函数延迟500s执行
window.addEventListener("scroll", debounce(lazyload, 500))

4. 骨架屏

骨架屏可以理解为当数据还未加载进来前,页面的一个空白版本。页面渲染完成之前,用户会看到当前页面的大致骨架。

下面的示例图,第一个是骨架屏,第二个是菊花图,第三个无优化。

生成骨架屏的方式:

  • 手写HTML,CSS定制骨架屏,但是维护成本高
  • 使用图片作为骨架屏: 小米商城的移动端页面
  • 自动生成并自动插入静态骨架屏 page-skeleton-webpack-plugin

5. 渐进式图片加载

知乎会用低分辨的模糊图片来做预览图,代替懒加载图片时用的logo占位图,预览图大小在2kb-3kb之间。

参考资料