jQuery.lazyload使用及源码分析

时间:2023-03-08 16:32:02

前言:

貌似以前自己也写过图片懒加载插件,但是新公司使用的是jQuery.lazyload插件,为了更好的运用,自己还是把源码看了遍,分别记录了如何使用,

插件原理,各个配置属性的完整解释,demo实例,源码分析(较简短),源码分析可以配合使用,配置属性,原理进行阅读,如需转载,请注明出处

博客园 华子yjh

一、如何使用

// 最简单的使用,不带参数
$('img').lazyload(); // 带参数(配置对象),下面配置对象中的各个属性值都是默认的
$('img').lazyload({
threshold : 0,
failure_limit : 0,
event : "scroll",
effect : "show",
container : window,
data_attribute : "original",
skip_invisible : true,
appear : null,
load : null,
placeholder : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"
});

二、内部原理

首先选中的img元素都绑定了一个appear事件(处理img显示真实的图片地址),方便以后满足条件时触发该事件;
在配置对象中有一个container属性配置,默认为window,如果img元素在该container容器视口中,则触发appear事件;
为了判断img元素是否在container容器视口范围中,造了如下四个*:

$.belowthefold = function(element, settings) {};    // 在视口下方
$.rightoffold = function(element, settings) {}; // 在视口右方
$.abovethetop = function(element, settings) {}; // 在视口上方
$.leftofbegin = function(element, settings) {}; // 在视口左方

看看源码中是如何利用这四个*:

if ($.abovethetop(this, settings) || $.leftofbegin(this, settings)) {
/* Nothing. */
}
// 不满足在上方,左方;也不满足在下方,右方; 则触发appear事件
else if (!$.belowthefold(this, settings) && !$.rightoffold(this, settings)) {
$this.trigger("appear");
}

三、配置对象中的其他属性

临界值,这个值是针对container容器的,即距离container容器视口的临界值

{
threshold: 0
}

事件,container容器默认绑定这个事件,在这个事件被触发时,会不断的判断img元素是否满足触发appear的条件,
因此当浏览器不停的滚动下来时,如果满足条件,则显示图片;

另外还有一点,如果这个事件不是scroll事件,则选中的img元素都会绑定这个事件,绑定的这个事件中同样会触发内部appear事件;

{
event: 'scroll'
}

显示方法,默认为show,也可以设置为fadeIn,API中隐藏了一个配置属性effectspeed,动画运行的时间

{
effect: "show"
}

img元素的一个data属性,用于存放图片的真实地址

{
data_attribute: "original",
}

忽略隐藏的img元素

{
skip_invisible: true
}

图片占位符,img元素默认src属性为1*1像素的透明图片

{
placeholder: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"
}

在img触发appear事件时执行的回调

{
appear: null
}

在img触发load事件时执行的回调

{
load: null
}

最后一个配置属性failure_limit

{
failure_limit: 0
}

为了便于理解,我们先来看一段与其有关的源码:

var counter = 0;

elements.each(function() {
if ($.abovethetop(this, settings) || $.leftofbegin(this, settings)) {
// ...
} else if (!$.belowthefold(this, settings) && !$.rightoffold(this, settings)) {
// ...
} else {
if (++counter > settings.failure_limit) {
return false;
}
}
});

什么意思呢,如果找到的是第 failure_limit 个img元素,且不在container视口上方,左方及视口内(可以允许在视口下方,右方),则中断循环

三、demo

看完原理和配置属性,是否觉得很简单呢,来看看几个demo吧

demo1 下拉滚动: http://jsfiddle.net/ddEPL/
demo2 Tab切换: http://jsfiddle.net/ddEPL/1/  http://jsfiddle.net/ddEPL/8/

四、源码分析

/*
* Lazy Load - jQuery plugin for lazy loading images
*
* Copyright (c) 2007-2013 Mika Tuupola
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* Project home:
* http://www.appelsiini.net/projects/lazyload
*
* Version: 1.9.3
*
*/ (function($, window, document, undefined) {
var $window = $(window); $.fn.lazyload = function(options) {
var elements = this;
var $container;
var settings = {
threshold : 0,
failure_limit : 0,
event : "scroll",
effect : "show",
container : window,
data_attribute : "original",
skip_invisible : true,
appear : null,
load : null,
placeholder : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"
}; function update() {
var counter = 0; elements.each(function() {
var $this = $(this);
// 如果图片隐藏,且忽略隐藏,则中断循环
if (settings.skip_invisible && !$this.is(":visible")) {
return;
}
if ($.abovethetop(this, settings) || $.leftofbegin(this, settings)) {
/* Nothing. */
}
// img满足在container视口中,则显示
else if (!$.belowthefold(this, settings) && !$.rightoffold(this, settings)) {
$this.trigger("appear");
/* if we found an image we'll load, reset the counter */
counter = 0;
}
// 如果找到的是第(failure_limit + 1)个img元素,且不在container视口上方,左方及视口内(可以允许在视口下方,右方),
// 则中断循环
else {
if (++counter > settings.failure_limit) {
return false;
}
}
}); } if(options) {
/* Maintain BC for a couple of versions. */
if (undefined !== options.failurelimit) {
options.failure_limit = options.failurelimit;
delete options.failurelimit;
}
if (undefined !== options.effectspeed) {
options.effect_speed = options.effectspeed;
delete options.effectspeed;
} $.extend(settings, options);
} /* Cache container as jQuery as object. */
$container = (settings.container === undefined ||
settings.container === window) ? $window : $(settings.container); /* Fire one scroll event per scroll. Not one scroll event per image. */
// 为container绑定scroll事件
if (0 === settings.event.indexOf("scroll")) {
$container.bind(settings.event, function() {
return update();
});
} this.each(function() {
var self = this;
var $self = $(self); self.loaded = false; /* If no src attribute given use data:uri. */
// 设置占位符
if ($self.attr("src") === undefined || $self.attr("src") === false) {
if ($self.is("img")) {
$self.attr("src", settings.placeholder);
}
} /* When appear is triggered load original image. */
// one绑定appear,触发后则移除该事件
$self.one("appear", function() {
if (!this.loaded) {
// 存在回调则触发
if (settings.appear) {
var elements_left = elements.length;
settings.appear.call(self, elements_left, settings);
}
$("<img />")
.bind("load", function() { var original = $self.attr("data-" + settings.data_attribute);
$self.hide();
if ($self.is("img")) {
$self.attr("src", original);
} else {
$self.css("background-image", "url('" + original + "')");
}
$self[settings.effect](settings.effect_speed); self.loaded = true; /* Remove image from array so it is not looped next time. */
// 更新elements,过滤掉已经加载的img元素,避免下次在update中轮循
var temp = $.grep(elements, function(element) {
return !element.loaded;
});
elements = $(temp); // 存在回调则触发
if (settings.load) {
var elements_left = elements.length;
settings.load.call(self, elements_left, settings);
}
})
.attr("src", $self.attr("data-" + settings.data_attribute));
}
}); /* When wanted event is triggered load original image */
/* by triggering appear. */
// 绑定不是scroll的事件,用于触发appear事件
if (0 !== settings.event.indexOf("scroll")) {
$self.bind(settings.event, function() {
if (!self.loaded) {
$self.trigger("appear");
}
});
}
}); /* Check if something appears when window is resized. */
$window.bind("resize", function() {
update();
}); /* With IOS5 force loading images when navigating with back button. */
/* Non optimal workaround. */
if ((/(?:iphone|ipod|ipad).*os 5/gi).test(navigator.appVersion)) {
$window.bind("pageshow", function(event) {
if (event.originalEvent && event.originalEvent.persisted) {
elements.each(function() {
$(this).trigger("appear");
});
}
});
} /* Force initial check if images should appear. */
$(document).ready(function() {
update();
}); return this;
}; /* Convenience methods in jQuery namespace. */
/* Use as $.belowthefold(element, {threshold : 100, container : window}) */ $.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;
}; $.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;
}; $.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();
}; $.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);
}; /* Custom selectors for your convenience. */
/* Use as $("img:below-the-fold").something() or */
/* $("img").filter(":below-the-fold").something() which is faster */ $.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);