WEB性能优化之图片预加载

前言

优化网络图片预加载方法

  • 前言
  • 正文
    - [并行下载的好处](#并行下载的好处)
    - [对预加载图片而言并非为一件好事](#对预加载图片而言并非为一件好事)
    - [一种更好的预加载方式](#一种更好的预加载方式)
    - [避免过早预加载](#避免过早预加载)
    
  • 小结
  • 关于作者

正文

利用浏览器多线程下载的原理, 预加载一组图片的一个通用的标准方式。
简化形式如下:

1
2
3
4
5
/* 'images' is an array with image metadata including a 'url' property */
for (var i = 0; i < images.length; ++i) {
var img = new Image();
img.src = images[‘url’];
}

这段代码遍历包含图片元数据的对象数组,为每个对象创建一个HTML Image对象,并为src属性设置url。一旦Image对象设置了src值,浏览器就会向服务器发起请求,并缓存返回的图片。

需要注意的是,浏览器请求是异步的。也就是说这段代码会遍历数组,每张图片几乎同时发起请求,并不需要等待服务器返回结果后顺序发起请求。对于现代浏览器而言,这段代码尝试并行下载4~8个张图片(当然如果来自不同域,会更多)。

并行下载的好处

网站通常会有一些资源,浏览器必须先下载后才能显式页面。HTML本身,一两个CSS文档,一些小的图片元素,字体,偶尔会有一些不可避免的JavaScript文件(需要在页面可以绘制前执行)。每一个文件通常都相当小,但是每个请求与服务器的往返都会导致延迟开销。虽然这种延迟通常很小,每个文档的延迟都是毫秒级的,但是如果浏览器需要等待一个请求完成后才能发起另一个请求的话,这些毫秒将依次累加,并迅速增至秒级,用户必须要等待这么长时间后才能浏览页面内容。如果可以同时发起所有请求,那么整体而言延迟时间会降低为一次的往返时延,从而使页面加载时间减少几秒钟。这并不会加快每个文档的实际下载速度,你仍受特定带宽的限制,所以4~8个的并行请求使下载速度降低4~8倍。但是总体下载速度的确加快了,因为你避免了连续的延迟开销。因为浏览器必须在下载了关键元素后才能绘制页面,避免顺序延迟时间累加意味着更快的页面绘制。

对预加载图片而言并非为一件好事

并行下载对于初始页面元素是非常有用的,因为浏览器在渲染页面前必须要先下载这些元素。一个CSS文件先于另一个CSS下载对于浏览器而言并无差别,因为浏览器需要两个文件都要下载后才能做其他事情。gallery里的图片预加载并非这种情况,你可以足够自信的预测哪个文件需要优先下载,你需要优先处理它,即使预加载的总体时间会稍长。

我网站的gallery的分析数据非常直观。虽然从一张图片切换至另一张图片的方式有多种,缩略图和上一张与下一张链接,90%的点击是在下一张链接上。几乎所有情况下,页面加载完成后的最关键因素是gallery里的下一张图片文件。如果使用标准的预加载方式,你无法控制这张图片何时加载。浏览器尝试加载gallery里的每张图片,以浏览器的最大并发请求组。这种技术很好的减少了gallery的总体加载时间,但也意味着加载最重要图片(下一张图片)的时间显著增长,因为它需要与其他并发请求竞争带宽。在相对缓慢的1.5Mbps DSL连接情况下,加载一张350K的图片需要2秒钟,浏览者有可能必须要等到4~6张图片加载完成后才能看到这种图片。也就是说gallery里的下一张图片可见前有一个潜在的12~15s的等待时间。有利的一面是这4~6张其他图片现在会被缓存起来,但是让用户盯着加载图片12秒钟,我们可能已经失去了这些用户。如果你以非「宽度连接」的方式测试你的网站的话,你可能会惊讶的发现,你的预加载器使你的网站对一些用户变得更糟。

一种更好的预加载方式

一旦理解了浏览者的行为,就可以设计一个预加载器,为大多数浏览者提供更好的体验。因为我知道几乎所有的浏览者是顺序浏览我的gallery,那么对我而言最好的策略是以相同的顺序加载图片。加载所有图片的总体时间可能会稍微长一些,因为我们会导致延迟开销累加,但此时的整体加载时间相对于页面的初始加载时间,并不是如此重要,因为在用户可以使用网站前,并不需要加载完所有的图片。我们只需要加载用户现在想看到的图片,javaScript如下:

1
2
3
4
5
6
7
8
9
10
11
12
function preload(imageArray, index) {
index = index || 0;
if (imageArray && imageArray.length > index) {
var img = new Image();
img.onload = function() {
preload(imageArray, index + 1);
}
img.src = images[index][‘serving_url’];
}
}
/* images is an array with image metadata */
preload(images);

注意:代码已做简化,生产环境代码会针对不同设备请求不同尺寸图片,并且会考虑用户进入gallery时非第一张图片的场景。

处理数组中第一张图片(index 0),添加onload事件处理函数,然后请求图片。只有当这张图片加载完成后,才会调用onload事件处理函数,然后为下一张图片做相同的操作,直到所有图片加载完成。

避免过早预加载

在用户请求一个页面和浏览器可渲染这个页面之间会发生很多事情。这段时间内,你的用户只能坐在哪里看着一个空白页面。如果你珍惜你的用户,你应努力缩短这个时间。要做到这一点,需要尽可能快的传输浏览器所需的渲染页面的最少数据。你的预加载器不应参与其中。

至少对我而言,预加载代码最好放在window onload处理函数内,如果使用jQuery的:

1
2
3
$(window).load(function() {
/* Preload code goes here */
});

小结

预加载分 并行预加载(普通预加载) 和 串行预加载 (优化预加载)方式。

关于作者

** 珠峰
WEB开发与管理相结合,注重技术与应用结合。现居上海。