现在这个年代还在写jQuery,我承认有点跟不上时代。但为什么说jQuery过时呢?个人感觉这篇剖析的比较浅显易懂:《jQuery真的过时了!》。但其实jQuery和其相关的插件中还是有很多实用技巧的,本文对jQuery.lazyload-1.10.0.js的源码进行分析,了解jQuery的注册事件。
废话不多说,直接源码
<div id="container">
<img class="lazy" data-original="img/bmw_m1_hood.jpg"><br/>
<img class="lazy" data-original="img/bmw_m1_side.jpg"><br/>
<img class="lazy" data-original="img/viper_1.jpg"><br/>
<img class="lazy" data-original="img/viper_corner.jpg"><br/>
<img class="lazy" data-original="img/bmw_m3_gt.jpg"><br/>
<img class="lazy" data-original="img/corvette_pitstop.jpg"><br/>
div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js">script>
<script src="jquery.lazyload.js?v=1.9.7">script>
<script>
$(function() {
$("img.lazy").lazyload();
});
script>
// jquery.lazyload.js?v=1.9.7
(function($, window, document, undefined) {
var $window = $(window);
// 定义初始化方法
// $(elem).lazyload(options)来进行调用
$.fn.lazyload = function(options) {
var elements = this;
var $container;
var settings = {
threshold : 0,
failure_limit : 0,
event : "scroll.lazyload",
effect : "show",
container : window,
data_attribute : "original",
data_srcset : "srcset",
skip_invisible : false,
appear : null,
load : null,
placeholder : "data:image/gif;base64,R0lGODdhAQABAPAAAMPDwwAAACwAAAAAAQABAAACAkQBADs="
};
// 触发加载图片事件前的预判断
function update() {
var counter = 0;
// elements => $('img.lazy');
elements.each(function() {
// this => elements[i]
var $this = $(this);
// 若设置项skip_invisible:true且元素不可见,则不执行返回
if (settings.skip_invisible && !$this.is(":visible")) {
return;
}
// 如果元素超过视窗顶部或左侧,则不做任何事情
if ($.abovethetop(this, settings) ||
$.leftofbegin(this, settings)) {
/* Nothing. */
}
// 否则,且不在下方也不在右侧,即元素在视窗内
else if (!$.belowthefold(this, settings) &&
!$.rightoffold(this, settings)) {
// 触发元素的appear事件,同时counter设置为0
$this.trigger("appear");
counter = 0;
// 否则,元素在视窗的下方或右侧
} else {
// 若counter+ 1后 > 设置项的failure_limit,则返回false
if (++counter > settings.failure_limit) {
return false;
}
}
});
}
// 整合用户设置和默认设置,最后settings是最终设定
if(options) {
// 适配了两种参数命名类型,将其统一为failure_limit、effect_speed
if (undefined !== options.failurelimit) {
options.failure_limit = options.failurelimit;
delete options.failurelimit;
}
if (undefined !== options.effectspeed) {
options.effect_speed = options.effectspeed;
delete options.effectspeed;
}
// 将用户设置options扩展给settings
$.extend(settings, options);
}
// $container是上下文的jQuery对象,即视窗,默认是$(window),当设置中又container时$(setting.container)
$container = (settings.container === undefined ||
settings.container === window) ? $window : $(settings.container);
// 默认情况下setting.event === 'scroll.lazyload'
// 如果设置的event参数是以scroll开头,则给视窗注册事件
if (0 === settings.event.indexOf("scroll")) {
// create or replace event,注册'scroll.lazyout'事件,并设置回调函数,执行预判断函数return update();
$container.off(settings.event).on(settings.event, function() {
return update();
});
}
// this === $elements
this.each(function() {
// self === element;$self === $(element)
// self是元素本身,$self是元素的jquery实例对象
var self = this;
var $self = $(self);
// 定义self中的loaded标志属性
self.loaded = false;
// 如果img没有src属性,则用settings.placeholder填充src
// placeholder是data:url图像参数
if ($self.attr("src") === undefined || $self.attr("src") === false) {
if ($self.is("img")) {
$self.attr("src", settings.placeholder);
}
}
// 给$self绑定只执行一次的appear事件,并注册事件触发方法
$self.one("appear", function() {
// this === self
// 如果元素没有loaded属性
if (!this.loaded) {
// 调用apper事件函数执行时的回调函数
if (settings.appear) {
// 所有元素的数量
var elements_left = elements.length;
// 执行setting.appear回调函数
// 回调函数中this === self是元素本身,elements_left是一共注册慢加载元素的数量,settings是设置的参数
settings.appear.call(self, elements_left, settings);
}
// 创建img元素
$("")
// 绑定只执行一次的load事件及触发方法
.one("load", function() {
// 获取original、srcset的图片url
var original = $self.attr("data-" + settings.data_attribute);
var srcset = $self.attr("data-" + settings.data_srcset);
// 如果original不等于img.src
if (original !== $self.attr("src")) {
// 隐藏原来的img
$self.hide();
// 如果$self是img
if ($self.is("img")) {
// 把original赋值给src
$self.attr("src", original);
// 如果srcset有值,则赋值给srcset属性
if (srcset !== null) {
$self.attr("srcset", srcset);
}
}
// 如果是video标签,则把original值赋给poster
if ($self.is("video")) {
$self.attr("poster", original);
}
// 否则给元素加背景图片
else {
$self.css("background-image", "url('" + original + "')");
}
// setting.effect === show => $self.show(speed);
// 配置图片出现的效果,默认是$(img).show()
$self[settings.effect](settings.effect_speed);
}
// 加载完成后将loaded标志调整为true
self.loaded = true;
// 真值判断,过滤数组,将已有loaded==true属性的元素排除
// 并将结果重新赋值给elements
var temp = $.grep(elements, function(element) {
return !element.loaded;
});
elements = $(temp);
// 元素load之后 若存在回调函数settings.load则执行
if (settings.load) {
var elements_left = elements.length;
// 回调函数中this === self是元素本身,elements_left是一共注册慢加载元素的数量,settings是设置的参数
settings.load.call(self, elements_left, settings);
}
})
// 绑定img的error事件及触发方法
.bind("error", function(){
$(self).trigger("error");
})
// 给img配置属性
.attr({
"src": $self.attr("data-" + settings.data_attribute),
"srcset": $self.attr("data-" + settings.data_srcset) || ""
});
}
});
// 如果event不是以scroll开头
if (0 !== settings.event.indexOf("scroll")) {
// 给每个$element元素绑定event事件
$self.off(settings.event).on(settings.event, function() {
// 事件中判断,如果self的loaded属性不为true,则触发appeaar事件
if (!self.loaded) {
$self.trigger("appear");
}
});
}
});
// 当窗口放大缩小的时候,视窗的大小变化同时需要检测是否需要加载
$window.off("resize.lazyload").bind("resize.lazyload", function() {
update();
});
// 手机pad设备适配
if ((/(?:iphone|ipod|ipad).*os 5/gi).test(navigator.appVersion)) {
$window.on("pageshow", function(event) {
if (event.originalEvent && event.originalEvent.persisted) {
elements.each(function() {
$(this).trigger("appear");
});
}
});
}
// 当页面加载后执行第一次事件预判断
$(function() {
update();
});
// 返回$elements
return this;
};
// 若视窗(容器)的的下边缘高度 <= 元素顶部距离[ -预加载距离 ] 即元素在视窗下方,则返回true
$.belowthefold = function(element, settings) {
var fold;
if (settings.container === undefined || settings.container === window) {
fold = (window.innerHeight ? window.innerHeight : $window.height()) + $window.scrollTop();
} else {
fold = $(settings.container).offset().top + $(settings.container).height();
}
return fold <= $(element).offset().top - settings.threshold;
};
// 若视窗(容器)的的右边缘距离 <= 元素左边缘距离[ -预加载距离 ] 即元素在视窗右侧,则返回true
$.rightoffold = function(element, settings) {
var fold;
if (settings.container === undefined || settings.container === window) {
fold = $window.width() + $window.scrollLeft();
} else {
fold = $(settings.container).offset().left + $(settings.container).width();
}
return fold <= $(element).offset().left - settings.threshold;
};
// 若视窗(容器)的的上边缘高度 >= 元素底部距离[ +预加载距离 ] 即元素在视窗上方,则返回true
$.abovethetop = function(element, settings) {
var fold;
if (settings.container === undefined || settings.container === window) {
fold = $window.scrollTop();
} else {
fold = $(settings.container).offset().top;
}
return fold >= $(element).offset().top + settings.threshold + $(element).height();
};
// 若视窗(容器)的的左边缘距离 >= 元素右边缘距离[ +预加载距离 ] 即元素在视窗左侧,则返回true
$.leftofbegin = function(element, settings) {
var fold;
if (settings.container === undefined || settings.container === window) {
fold = $window.scrollLeft();
} else {
fold = $(settings.container).offset().left;
}
return fold >= $(element).offset().left + settings.threshold + $(element).width();
};
// 判断元素不在视窗上下左右,即元素在视窗中间
$.inviewport = function(element, settings) {
return !$.rightoffold(element, settings) && !$.leftofbegin(element, settings) &&
!$.belowthefold(element, settings) && !$.abovethetop(element, settings);
};
// 给jQuery扩展选择器
$.extend($.expr[":"], {
"below-the-fold" : function(a) {
return $.belowthefold(a, {
threshold : 0}); },
"above-the-top" : function(a) {
return !$.belowthefold(a, {
threshold : 0}); },
"right-of-screen": function(a) {
return $.rightoffold(a, {
threshold : 0}); },
"left-of-screen" : function(a) {
return !$.rightoffold(a, {
threshold : 0}); },
"in-viewport" : function(a) {
return $.inviewport(a, {
threshold : 0}); },
/* Maintain BC for couple of versions. */
"above-the-fold" : function(a) {
return !$.belowthefold(a, {
threshold : 0}); },
"right-of-fold" : function(a) {
return $.rightoffold(a, {
threshold : 0}); },
"left-of-fold" : function(a) {
return !$.rightoffold(a, {
threshold : 0}); }
});
})(jQuery, window, document);
在代码115行左右又一段$(’’).one(‘load’)的事件注册代码。当此事件触发后才会真正的把图片加载进来。
但$(’’)是一个新元素,此元素也从未插入到DOM中,那么是什么时候被浏览器加载的呢?
这里有个知识点需要注意:当给一个img元素赋值src,且地址中的图片确实能被访问的时候就会触发此img的load事件。
所以在代码中可以看到在.one()后面执行了.attr()方法,将实际的图片地址赋值给src,从而触发load事件。同时,还进行了实际图片地址可访问的检测。
传送门:delete 操作符 - JavaScript | MDN
知识技能获取,感谢[网易云课堂 - 微专业 - 前端高级开发工程师]运营团队。