提高页面加载体验——延时加载你的图片


这是一篇迟到的文章,足足迟到了一个月,所以我对于自己的拖延症已经放弃治疗了……那么除了前几天找工作和黏豆包的离开,还有什么事情占用了我写博客的宝贵时间呢?奥特兰山谷!那个夺得荣誉的地方!为了联盟!

好了好了,看得出你们一脸鄙夷,我承认我都去打魔兽了……希望今天在国庆节尾巴翻然悔悟的我能得到大家的原谅(据说只要不睡就还算假期)。

扯了两句皮,回到我们今天的正题,延时加载图片。为什么要延时加载图片?产品展示网页通常会包含很多图片,但一般情况下也不会有太大问题,因为这些图片都不算太大,如果用户网络较好,是不会出现糟糕的体验。那么有没有包含很多超大图片的网页呢?有,比如苹果的 iPhone 介绍页面。这些网页为了体现出高清漂亮的产品照片,包含了大量的大尺寸图片,这些图片不夸张地说下载下来都可以直接做壁纸用。那么这样的网页在加载时会出现什么问题呢?想来大家都有体会,曾经苹果的页面中图片加载得十分糟糕,用户经常能看到加载一半的图片,更糟糕的是,这些图片还没有被完全加载完毕就被另一张加载一半的图片代替了——比如那些华丽的轮播图。

不过大家有没有发现,苹果现在的网站不会再出现这个问题了,整张网页从打开到最后加载完备,整个过程犹如牛奶般丝滑……

这是什么 magic tech ?先卖个关子,为什么我会研究起这个问题?因为我目前实习的公司也遇到了这个问题……我们的首页也包含了很多图片,切换起来也有苹果曾经遇到的问题,但是人家苹果改进了呀,所以本着见贤思齐的原则,我把苹果的页面加载过程彻彻底底研究了一遍。

总的来说,网页中加载图片有两种方法,一是直接用 img 标签引用,二是设置背景图片。早期的浏览器(IE8 及以前)加载资源的顺序是顺序加载,即在 DOM 前面引用的资源会被优先加载,这也是目前很多书籍推荐将 script 紧贴 body 标签闭合之前的原因,这样能够 script 最后加载而不阻断 DOM 的渲染。

如果媒体也是按照 DOM 引用的顺序来加载也不会出现什么问题,排在 DOM 后面的图片通常也排在页面的后部,而用户刚刚打开页面时它们也通常不在屏幕可视范围内,大可慢慢加载,但是现在浏览器对资源加载做了优化,浏览器按照自己的判断加载资源,而图片通常会被同时并行加载——这是个很棒的 feature ,这提高了页面加载速度,但也会导致上面提到的问题——排在前面的图片不会最先加载完毕,用户会看到加载一半的残图。

问题分析完毕,我们来想想解决办法。问题出在浏览器对媒体文件“自作聪明”地并行加载,所以解决办法就是想方设法让浏览器慢点来——加载好了一部分图片后再加载另一部分,把整体并行改为整体穿行局部并行。

对于背景图片的引用,只需要在 CSS 中用 background: none!important; 覆盖掉就好,这样背景图片就不会被加载了,当时机合适,再移除相关的 className 就好。那么对于 img 标签直接引用的图片呢,也很简单,只需要将图片地址不写在 src 里就可以了,比如写在 data-delay-src 里,然后用 JS 去读这个属性值,在合适的时机里赋给 src 就可以了。

但是呢,用户也不希望进来的时候发现页面上一片空白对吧,所以呢,在页面第一屏区域的图片就不要处理了,让它们在用户打开页面的第一时间呈现出来,第一屏的图片也不会太多,不会过于影响体验。待第一屏图片加载完毕后就可以再顺次加载第二屏、第三屏的图片了。

下面是我优化前后的 Smartisan 官网首页加载效果的对比,需要注意的时,我在 Chrome 开启了模拟网络环境,导致加载速度被刻意放慢,这是为了更加显著地体现差别,实际上不会这么慢。

上面的视频是优化前的加载过程,下面的视频是优化后的加载过程,是不是看起来顺滑多了?有心的你一定发现了其他的不同——优化前页面 loading 时前面转圈的加载图没有显示出来,优化后的显示正常。这是因为浏览器会在 DOM 渲染完毕后才去加载图片资源,如果你使用了 AngularJS 的 ngView 配合路由异步请求模板,这个 loading 的图很可能是在你请求到模板后才能显示出来,这样这个 loading 图基本就永远不会被看到了。我解决的方法是用 base64 URL 代替外部地址,这样图片是随 DOM 下载好的,自然也会在第一时间被显示出来。

另外还有个有趣的地方,就是我在优化页面的时候,为了尽快让浏览器标签上的 loading 停下来(长时间转的话别说处女座的同学了,我自己都受不了),所有延时加载的图片都是在 window.onload 触发后加载的,这样也会导致在用户强刷新时,这些被延时加载的图片依然顽固地从缓存里蹦出来,下面看看优化前后资源加载的 timeline 。

左侧是优化前的,共请求了 3.9MB 资源,耗时 21.7s ,优化后请求了 2.0MB 资源,耗时 11.76s ,至于这算一个 bug 还是算一个 feature 呢,就请各位看官自行决定吧,但我们最后是按 feature 对待的(CDN 很贵的哎~)。

如果你觉得看视频不过瘾,那么请在新标签中敲入 t.tt ,体验线上效果!如果不出意外,你将会看到:很快就加在完毕的标签(标签上的 loading 图很快消失),渐变淡入的首屏图片,自然丝滑的图片切换,强刷新依然傲娇地读取缓存(from cache)。

我要是在这里结束这篇文章,那么这绝对是一篇不折不扣的炫耀文章!瓜皮菜叶鸡蛋壳估计是少不了的!我知道你们希望弄懂背后的理论,但我更相信你们渴望一套现成完备的解决方案,所以…… https://github.com/Sneezry/image-delay.js 上吧,霹雳贝贝们!咦?肿么全是英文?哎呀,允许人家偶尔装一下好不好~(不写中文教程你会咬我吗?不咬是吧,不咬就不写了哈~)

最后安利一部电影,Mad Max: Fury Road,里面 Nux 小哥好帅啊~(虽然是一个 half-live “活死人”,有的人就是这样,演个僵尸啥的也辣么耐看……)