jquery lazyload源码分析

这是一款很老很有名的插件了,专门用于图片延迟加载,github地址如下:

https://github.com/tuupola/jquery_lazyload

下面,我摘取比较重要的代码说明一下它的实现思路,代码量非常小10分钟就可以看完:

基础原理

如果一个网页很高、图片很多,那么浏览器就会并行的下载图片,对网速一般的用户是体验很差的,为什么呢?

  • 图片加载是并发的异步的,不同位置的图片加载完成的次序就会不同,可能导致网页上面的图片还没下载完,后面的就完成了,造成一种不流畅感。
  • 正因为图片并发下载,用户带宽和服务器带宽有限的情况下,这样的资源竞争可能导致整体加载时间拖长。

那么图片延迟加载是怎么优化这个问题呢?

很简单,当图片区域进入视野的时候才让其进行下载,那怎么实现呢?我们知道,<img src=””>一旦设置src属性,浏览器就会启动下载,因此插件的思路是不设置src属性,而是等待<img>区域进入视野后再设置src属性。

因此,lazyload插件让我们把图片的url写在其他属性里:

加载时机

插件会在浏览器ready事件触发后,搜集所有<img>标签保存成数组,现在的问题就变成了:什么时机设置这些<img>的src属性呢?

插件选择了2个时机,一个是浏览器的scroll事件,一个是浏览器的resize事件,意图很容易理解,通常只有这2个行为会导致图片从视野之外进入到视野之内。

滚动事件scroll

窗口尺寸变化resize

检测方法

当上面的事件发生的时候,我们知道可能某些图片进入了视野,那么具体是哪些图片进入了视野呢?

那就是update方法的事情了:

elements是网页中所有的<img>标签数组(Jquery数组),遍历所有<img>标签判断哪些<img>在视野范围内即可。

  • 第一个if分支:表明该<img>在屏幕上方之外或者在屏幕左侧之外则什么也不用做,否则说明<img>在屏幕上方之内与屏幕左侧之内,需进入下面的第二个分支。
  • 第二个if分支:判断<img>是否在屏幕下方之外或者屏幕右侧之外,如果不是则说明<img>就在视野范围内了,立即给<img>触发自定义的appear事件以便进一步处理。
  • 第三个if分支:如果<img>在屏幕下方或者屏幕右侧之外,则停止each遍历,这是什么意图呢?为了减少遍历的性能损耗,下面具体说说。

首先,这个选项的出现主要是因为每次滚动scroll都要遍历elements数组中所有的img,判断它们哪些进入了视野,这本身是很耗费性能的,因此如何机智的停止遍历就很重要了。

elements数组最初是采用jquery的选择器语法获取的,它按照深度遍历方式从上至下,从外至内的遍历DOM节点搜集img标签,因此当发现图片在屏幕下方或右侧之外的时候,作者认为剩余的图片按照常规的DOM顺序都应该在视野之外,就不必再继续判断后续的img了(可以参考github issue)。

当然,作者这个优化的前提是DOM结构比较简单(比如自上而下的新闻图片),对于复杂的DOM结构(比如纵横2个方向都有滚动条)是可能出现误判导致视野内的图片无法加载的,因此该插件可以提供一个阀值failure_limit来控制是否可以额外探测一些img标签。

加载图片

其实在初始化插件的时候,已经为elements数组里的所有<img>绑定了appear事件处理函数:

这里this是用户传入的的jquery元素选择器,等价于elements数组,收集了符合选择器的<img>标签。

当一个<img>标签触发了appear事件后,说明该<img>已经进入视野,因此插件会通过$(‘<img />’)创建一个临时的img DOM节点,并设置它的src属性为真实图片地址,这样浏览器就会去下载这个图片了。

一定要注意,这里并不是直接设置<img>标签的src,而是创建一个临时img并设置其src从而下载图片,这样做的目的是为了有机会控制<img>图片的淡入淡出效果。

当临时img下载图片完成时,浏览器会触发load事件通知图片加载完成,这时候浏览器已经将图片保存在本地并且缓存,因此这时候给原始的<img>设置src属性可以保障图片立即渲染。

插件提供图片的淡入效果,具体做法就是先把<img>通过hide()隐藏起来,然后接着调用了可配置的过渡函数,插件默认调用show()方法,并传入一个过渡动画时间参数,这样就实现了淡出的感觉。

当然,接下来要标记这个<img>的loaded=true,这背后也是有意图的:当一个未加载的<img>进入视野后,插件创建临时img进行图片加载,但是我们知道下载图片是需要时间的,极有可能load完成回调可迟迟没有到来,这时候再次滚动会再次触发该<img>的appear事件,按逻辑又会创建另外一个临时img加载同样的图片,因此第一个加载完成的回调中将loaded设置为true,可以跳过后续重复的没有意义的load回调逻辑。

另外,为了减少elements的大小,从而减小每次update函数的遍历成本,所有加载成功的<img>标签都将从elements数组中移除,这里用的jquery的grep方法将返回一个过滤后的数组,而$(数组)将返回一个jquery包装的elements数组对象,可以参考grep方法说明

关于jquery lazyload插件就说到这里,另外值得参考的是lazyload封装成jquery插件的方式。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~