小白的理解——图片懒加载

开局先出张嘴

​ 懒加载(lazyload),即延迟加载,是一种对网页图片进行延迟加载处理的技术。说这货之前,先来碎嘴几句传统的网页加载方式。众所周知,浏览网页,经常会看到很多图片。正常情况下,浏览器会从上到下的加载<img src='xxx.jpg'> 的图片标签,将他嵌进它原本应该在的地方。这样会导致什么问题呢?

​ 当用户只是想打开个网页登录一下打个酱油就下了的时候呢,浏览器还在默默的加载着从服务器那端下载回来的页面,即使绝大部分图片是在用户不往下滚动页面就看不到的地方。这样势必就会造成资源的浪费,首先浏览器默默地加载完这些图片,用户看都不看做白工不说,服务器端也是亚历山大啊,尼玛每次一来请求都要发送网页的全部图片过去,用户不心疼流量,我心疼流量啊。特别是淘宝、京东这些图片数量非常巨大的电商,光首页就第一屏就得几十张图片了,如果还用传统的这种方式加载图片的话,那么用户体验是相当差的。所以这里就要说一下懒加载的工作原理了。

啥是懒加载呢

​ 看名字就可以知道了,就是懒嘛。懒到什么程序呢?用户不看他,他就不加载了。这其实就是跟我们小时候做暑假作业是一样的道理,不到暑假最后一天,暑假作业一页都不会翻。

​ 拥有同样的态度的呢,就是我们的懒加载同学了,在用户没看到他的时候,他输出的只是一张不是很大内存的占位图,而当用户往下划拉滚轮,屏幕将要看到懒加载同学的时候,这位同学才姗姗向服务器发送请求,下载真正的图片。这样的按需加载,造成的结果就是,服务器端压力变小,用户端节省了大笔流量的同时还提高了整体流畅度,真可谓双赢啊。所以,可见,懒有时候也是一种好事啊。

是时候展现真正的技术了

​ 那么问题来了,前面说的这么酷炫,具体从技术方面,怎么实现呢?

​ 本文给出一种利用jQuery库实现图片懒加载的原理,具体操作呢。

​ 首先,img元素的src属性值替换为占位图,真正的图片地址保存在自定义的属性当中,如下:

1
<img src"images/placeholder.jpg" data-src="images/我是真正的图片地址.jpg">

​ 因此,在页面中显示的图片,其实就是一张占位图啦,在页面某处你还没看到的地方。当你往下拉页面的时候,看到这个图片的时候,利用jQuery去下载真正的图片,所以当网速非常快的时候,你压根就感知不到懒加载的动作。

​ 所以问题似乎就变得简单了,等用户看到相应图片的时候,再把图片的src属性值替换为data-src的属性值就行了。这里就需要相应的编程语言跟计算机进行交互了,你不可能用大白话跟电脑说,它就听的明白的,用英语也不行。

​ 所以就需要将人话翻译成编程语言了,首先,用户看到图片的情况呢,就是页面垂直方向的下拉距离加上垂直方向用户可见的区域距离大于图片所在的位置相对于页面的垂直距离了。

如图:

​ 也就是在 a+b>c成立的条件下,就可以加载真正的图像了;

​ 所以结合相应的API,说浏览器能听的懂的话就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 需要引入jQuery -->
<img src="img/placeholder.jpg" data-src="img/true1.jpg" class="lazy">
<img src="img/placeholder.jpg" data-src="img/true2.jpg" class="lazy">
<img src="img/placeholder.jpg" data-src="img/true3.jpg" class="lazy">

<script>
function lazyload(){
var $win = $(window);
$('.lazy').each(function(){
// a+b >=c的话
if($win.scrllTop()+$win.height()>=$(elt).offset.top){
// 成立即可加载真正的图片
this.src=$(this).data('src');
}
})
}
</script>

​ 好的,截止目前,我们给出了一种判断图像是否暴露在用户视野,如果是就加载真正图像的方法。

​ 接下来,就要告诉浏览器同学,让她应该在什么时候,用这个方法来判断图片是否已经进入可视区域。记住,她已经知道如何做了,只是还不知道什么时候做。

​ 这时候,又再一次结合相应API,说浏览器话就是:

1
2
3
4
$(window).on('scroll resize',function(){
// 让浏览器在页面发生滚动或者窗口尺寸发生变化的时候触发lazyload方法
lazyload();
})

​ 进展到这里,浏览器同学已经可以很好的进行懒加载了,你仿佛已经看见了在服务器机房维护的同事脸上露出了的欣慰,看到在办公室打高尔夫的老板脸上表露出的赞许,感受到用户可以自由自在打酱油的舒适,还有楼下卖早茶的小美终于打算接受你每天买早茶打八折的建议……

最后再做点什么

​ 优化,再做点优化,或者封装成一个模块或者是插件,让你可以早一点下班。

优化一

​ 首先,浏览器会在每次用户滚动鼠标滚轮的时候,不停的触发事件并调用lazyload方法,这个无形之中可是会对性能打上几个这口的啊,所以有必要对他进行一些事件的稀释。

​ 这个时候,又结合相应的API,说下浏览器能懂的话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// I'm global
var timer = null; // 用于存放定时器对象
$win.on('scroll resize',function(){
4if(timer) return; // 当timer对象不为null的时候,直接退出函数

4timer = setTimeout(function(){
44load();
44console.log('test');
44timer=null;
4},250)
})
// 虽然我是注释,但我也挺重要的
// 这个骚操作能让用户在250毫秒内只能触发一次滚动事件
// 从而将事件流大大稀释
优化二

​ 对已经加载了的图片,不进行lazyload判断,一些已经加载好了的图片,他又不会无端端地变为占位符,所以这个时候再循环遍历他们进行判断,也是会稍稍影响性能的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在全局中找一个容器变量
var $container = $('.lazy');
// 在lazyload函数中对需要进行懒加载的对象数组进行'瘦身'
function lazyload(){
var $win = $(window);
$container.each(function(){ // *
// a+b >=c的话
if($win.scrllTop()+$win.height()>=$(elt).offset.top){
// 成立即可加载真正的图片
this.src=$(this).data('src');
this.loaded = true; // 添加一个‘标识’+
var tmp = $.grep($container,function(item){
// 返回未被加载过的图片
return !item.loaded;
})
$container = $(tmp);// 对图像数组进行瘦身
}
})
4}
优化三

​ ok,已经加载完的图像也不会再进行判断了,那么如果我一个网页图像全部都加载完毕了,难道window对象还要绑定scroll resize这些时间吗? 当然不啦,接下来做的优化就是,当所有图像都加载完的时候,销毁绑定的事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function lazyload(){
4//之前的一些代码,为了节约你的流量,我就不重复写了

//添加判断方法
if(isAllDone()){
destructor();
}
}
function isAllDone(){// 判断lazy图像是否都加载完毕
// 全部图像加载完,很明显 $container变量会被榨干
return $container.length==0;

}
}
function destructor(){
$(window).off('scroll resize',lazyload);
}
优化四

​ 接着进行相应的封装或者写成插件,就可以拿来复用,以后就可以早点下班啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
(function($){

4function LazyLoad(el,opts){
44this.init(el,opts);
4}
4// 默认的data数据名称
4LazyLoad.DEFAULTS={
44dataAttr : 'src'
4};

4//定义init方法
4LazyLoad.prototype.init=function(el,opts){ // 初始化
44this.$el = $(el);
44this.opts = $.extend({},LazyLoad.DEFAULTS,opts);// 不能用this.DEFAULT,这里this指代的不是LZ对象
44this.$win = $(window);

44// 当页面加载完毕的时候,检测下是否有图片显示在可视区域内
44this.load();
44// 为window对象绑定事件
44this.bindEvent();
4};

4LazyLoad.prototype.bindEvent=function(){ // 为Windows绑定对象
44var self = this;
44var timer = null;

44this.fn=function(){
444// this.load(); 这里的this指代的是window对象
444if(timer) return;

444setTimeout(function(){
4444self.load();
4444timer=null;
444},250)
44}
44// 不传实名参数的原因在于,这样相当于实名制,可以是找到window两个事件中的handler,从而可以销毁掉两个事件
44this.$win.on('scroll resize',this.fn);
4};

4LazyLoad.prototype.load=function(){ // 加载图片
44var self = this;
44var $el = this.$el;
44$el.each(function(){
444if(!this.loaded){ // 图片已经被加载的时候,就不再判断是否在可是区域了
4444if(isVisibled(this)){
44444appear(this);
4444}
444}
444// 如果所有图片都已加载,那么执行销毁事件函数
444if(isAllLoaded()){
4444self.destructor();
444}
44});
44// 判断所有图片是否已全部加载函数
44function isAllLoaded(){
444return $el.length==0;
44}

44// 位置检测
44function isVisibled(elt){
444var $win = self.$win;
444// 这里的this指代的是window
444return $win.height() + $win.scrollTop() >= $(elt).offset().top;
44}

44// 显示图片
44function appear(elt){
444if(isVisibled(elt)){
4444elt.src=$(elt).data(self.opts.dataAttr);
4444// 当图片已加载的时候 给一个loaded为true的标记
4444elt.loaded = true;

4444var tmp = $.grep($el,function(item){ // 对$el这个对象数组进行瘦身
44444// 返回为被加载过的图片
44444return !item.loaded;
4444})
4444$el = $(tmp);
444}
44}
4};
4LazyLoad.prototype.destructor=function(){
44this.$win.off('scroll resize',this.fn);
4}

4

4$.fn.extend({
44lazyload:function(opts){
444new LazyLoad(this,opts);
444return this;//这里为了连缀,所以返回了this
44}
4});
})(jQuery)

结语

​ 一天到晚都在输入知识,一直想着找些什么地方来输出,加上自己在生活中也常常有很多看法和感受,没什么地方抒发,故开通了个人公众号,想着做点笔记备忘的同时,还能有一席之地发声。

ok,先这样啦,下次见