图片懒加载的jQuery插件lazyLoad源码分析十八问

问题1:如何为每一个调用对象DOM元素添加自定义事件?

this.each(function() {
            var self = this;
            var $self = $(self);
            self.loaded = false;
            /* If no src attribute given use data:uri. */
            //如果该DOM元素的src属性为undefined或者src是false,那么如果该元素是img
            //那么该元素的src设置为placeholder,也就是img默认的src是placeholder,但是只有
            //在用户没有为img指定src的时候src才是placeholder! 
            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. */
            //为每一个元素绑定appear事件,该事件只会执行一次!
            $self.one("appear", function() {
            	//如果元素还没有触发loaded事件,第一次绑定的appear的时候loaded是false!
                if (!this.loaded) {
                	//appear默认是null,第一次绑定的时候不会执行这个if语句!
                    if (settings.appear) {
                        var elements_left = elements.length;
                        settings.appear.call(self, elements_left, settings);
                    }
                    //创建一个img对象,检测该对象的load事件。之所以用一个新的img是因为我们只要用该img加载data-original中保存的
                    //图片真正的地址,即把src替换为data-orinal的值,只要该新img已经完成了,那么该元素已经添加了一个只会调用
                    //一次的事件了,名称为appear!
                       $("<img/>").bind("load", function() {
                           //获取默认的data-orignal的值!
                            var original = $self.attr("data-" + settings.data_attribute);
                            //把调用对象首先隐藏,然后默默加载
                            $self.hide();
                            
                            //如果元素是img,那么把该元素的src设置为original值,所以data-original才是真正的
                            //图片的URL,而src只是占位符!
                            //如果是其它的元素非img,那么就设置background-image
                            if ($self.is("img")) {
                                $self.attr("src", original);
                            } else {
                                $self.css("background-image", "url('" + original + "')");
                            }
                            //this['mouseover'](400)表示这个事件的调用时间
                            $self[settings.effect](settings.effect_speed);
                            //这时候调用成功了,DOM元素的loaded设置为true!
                            self.loaded = true;

                            /* Remove image from array so it is not looped next time. */
                            //grep注重于筛选,第三个参数是false,表示筛选loaded是false的调用对象的集合!
                            var temp = $.grep(elements, function(element) {
                                return !element.loaded;
                            });
                            //把没有加载完成的DOM合并为jQuery集合!
                            elements = $(temp);
                             //load默认是null,表示调用该函数,上下文是DOM,第一个参数是还有多少个元素没有加载完成,第二个参数是最终settings对象
                            if (settings.load) {
                                var elements_left = elements.length;
                                settings.load.call(self, elements_left, settings);
                            }
                        })
                        //bind方法返回值是调用对象,也就是创建的img对象,我们给该img设置了src属性为data-original的值!
                        .attr("src", $self.attr("data-" + settings.data_attribute));
                }
            });

note:注意上面的appear自定义事件是通过one来绑定的,该函数只会被调用一次,而且触发了appear事件时候回调函数中用data-original值替换了原来的src的值,所以说data-original中存放的才是真正的图片的URL!

问题2:为什么我们需要动态创建img对象,这是为了使得当事件触发的时候不用从服务器下载图片,而是从本地直接获取图片?

解答:没错

                     $("<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. */
                            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));
note:我们要知道我们是首先给元素添加load事件的,同时bind方法返回的对象就是调用对象,也就是我们创建的img对象。绑定了load事件以后我们才设置了该imagedioxide的src属性,这样做就是为了把图片下载到本地,也就是 图片预加载功能。当我们触发自定义appear事件的时候不用等待从服务器获取图片,而是从本地直接获取到了!有一点要格外注意:新图片元素不一定要从添加到文档才开始下载,只要设置了src属性就开始下载!
JS原生实现了图片预加载:

 $(document).ready(function()
   {
     var img=new Image();
     //这里我们没有通过document.body.appendChild把该img对象添加到DOM树中,仅仅为了提前把图片下载到本地!
     img.οnlοad=function(e)
     {
       var event=e?e:window.event;
     }
     //元素不一定是从添加到文档才开始下载,只要设置了src属性就会开始下载!
     //但是对于图片来说,我们必须在指定src属性之前指定事件!
     //如果仅仅是为了图片的预先加载,那么我们不需要添加到DOM树中!
     img.src="img/viper_1.jpg";
   })
问题3:只有元素真正出现在视口的时候才会去加载图片,这样做的原因是为了防止其它我们不需要的图片在我们没有滚动的到的时候也被加载了,这就是懒加载的真正含义。只有我们需要的时候才会去加载?

解答:正确

   $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. */
                            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));
                }
            });
注意:还是上面同一段代码,我们可以看出只有在触发了appear事件的时候,也就是图片出现在视口中的时候才会去预先加载图片,这样做的目地是在onload事件处理程序中直接读取本地的图片,而不用去服务器端读取,从而实现图片加载的流畅,同时也能避免加载不需要的图片资源。我们必须知道,在调用bind方法时候我们只是给该图片绑定了load事件,而在设置了src属性后才会去加载图片,所以说onload事件在设置了src之后才会触发,因为onload只要在图片加载完成才会触发,而图片加载完成必须要先设置src属性!
问题4:本插件设置了两个回调函数,appear和load,其中appear只要元素第一次出现在视口中就会调用,而load会在图片第一次加载完成也就是图片触发了load事件的时候会回调?
appear在图片第一次出现在视口中回调

          if (settings.appear) {
                        var elements_left = elements.length;
                        settings.appear.call(self, elements_left, settings);
                    }
load会在图片加载完成,也就是onload事件中被调用

                if (settings.load) {
                                var elements_left = elements.length;
                                settings.load.call(self, elements_left, settings);
                            }

那么如何配置这两个函数

appear:function(left,settings)
      {
      //这里面的this是当前DOM对象,这里的left是包含当前元素的,也就是当前元素还没有appear之前被调用的!
        console.log(left);
      },
      load:function(left,settings)
      {
      //这里面的this是当前DOM对象,这里的left表示不包含当前元素的,也就是当前元素已经appear了不算在其中!
        console.log(left);
      }

注意:我们看到两个函数上下文都是调用对象的DOM,第一个参数是还没有出现在视口中的元素的个数,第二个参数是setting也就是默认的配置和用户自定义的配置组成的新的object对象!特别注意:这两个函数只有在当前DOM第一次出现在视口中才会调用,以后不会再次调用了!

问题5:如何知道当前还有多少元素没有出现在视口中,该插件是如何完成剩余元素的更新的?

/* Remove image from array so it is not looped next time. */
                            var temp = $.grep(elements, function(element) {
                                return !element.loaded;
                            });
                            elements = $(temp);
注意:从这里我们可以知道,每次只要有元素出现在视口中,也就是触发了appear事件,就会更新调用对象,只有那些还没有出现在视口中的元素才会留在该集合中。参照 jQuery.grep源码就会知道,第三个参数是false,只有那些返回true的DOM元素才会在集合里面,也就是element.loaded是false(没有加载过)才会留下来。这是为了防止出现在视口中了下次还会执行不必要的代码逻辑。

问题6:我们可以对元素的background-image实现懒加载?

解答:可以

             if ($self.is("img")) {
                                $self.attr("src", original);
                            } else {
                                $self.css("background-image", "url('" + original + "')");
                            }
问题7:如果我们设置了效果,那么可以设置该效果的变化时间,如fadeIn传入参数100ms?

解答:可以

如下配置effectspeed控制动画的时间

effectspeed:1000,
effect:"fadeIn"

我们看看源码中如何调用,不过可惜的是源码并没有为该效果指定回调函数

 $self[settings.effect](settings.effect_speed);
问题8:我们可以指定skip_invisible参数表示对于不可见的元素全部跳过,也就是对隐藏的元素不判断他是否在可视区域之内?

           elements.each(function() {
				//DOMContentLoaded触发后调用,resize时候调用,如果是scroll那么每滚动一次都会在container
				//上面调用一次update方法(默认就是scroll,如果用户没有指定自己的事件就是scroll事件)!
                var $this = $(this);
				//如果用户指定了skip_invisible,那么对于invisible的元素直接返回!
                if (settings.skip_invisible && !$this.is(":visible")) {
                    return;
                }
                if ($.abovethetop(this, settings) ||
                    $.leftofbegin(this, settings)) {
                        /* Nothing. */
                } else if (!$.belowthefold(this, settings) &&
                    !$.rightoffold(this, settings)) {
                        $this.trigger("appear");
                        /* if we found an image we'll load, reset the counter */
                        counter = 0;
                } else {
                    if (++counter > settings.failure_limit) {
                        return false;
                    }
                }
            });

如果直接return那么表示跳过该元素

$(document).ready(function()
   {
     var $arr=$('div');
     $arr.each(function(i,elem)
     {
        if(elem.id=="n2")
        {
          return;//这个return表示跳过该元素直接循环下一个元素,也就是对于id为n2的元素直接跳过!
        }else
        {
           console.log(elem);
        }
     });
   })

注意:对于 each函数来说,如果返回值是false表示跳出循环,也就是剩余的元素都不会执行特定的函数,但是如果直接return那么表示跳过该元素而直接遍历后面的元素。
问题9:update方法是该插件的核心,他用来判断什么时候应该触发我们在调用对象上面绑定的自定义事件appear,如果触发了这个appear事件,那么表示该元素已经在视口了,这时候我们就直接加载图片就可以了?


问题10:什么时候会检查元素是否在可视区域之内,然后决定是触发自定义的事件还是什么也不做?

默认的事件是scroll,这时候滚动一次就会判断一次是否在可视区域之内

 if (0 === settings.event.indexOf("scroll")) {//如果自己定义事件如mouseover那么indexOf返回-1,不会执行这里的if语句!
            $container.bind(settings.event, function() {
                return update();
            });
        }
如果窗口大小发生变化了,那么我们需要检查是否有元素出现在视口中间

 /* Check if something appears when window is resized. */
        $window.bind("resize", function() {
            update();
        });
只要触发了DOMContentLoaded就会检查是否有元素出现在视口中

/* Force initial check if images should appear. */
        $(document).ready(function() {
            update();
        });
问题11:现在我们需要判断元素是否在可视区域之内,这时候依据是什么呢?

临界条件用于触发自定义appear事件表示元素已经在可视区域之内

     if (!$.belowthefold(this, settings) &&
                    !$.rightoffold(this, settings)) {
                        $this.trigger("appear");
                        /* if we found an image we'll load, reset the counter */
                        counter = 0;
                }
我们首先来看看belowthefold进行的判断,他用于判断元素在垂直方向上是否已经在可视区域内了,返回false表示垂直方向已经出现在可视区域内了

 /* Convenience methods in jQuery namespace.           */
    /* Use as  $.belowthefold(element, {threshold : 100, container : window}) */
    $.belowthefold = function(element, settings) {
        var fold;
		//如果我们的container是window,那么我们获取该元素的innerHeight(可视区域的高度,但是不包括顶部工具栏,适用于IE9+),
		//但是IE8等浏览器用$window.height()获取窗口的高度。然后加上scrollTop属性表示窗口的高度和滚动的距离的高度!
        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();
        }
		//如果返回true,表示元素距离文档的距离大于(窗口滚动距离+窗口高度),也就是表示元素还没有出现在视口中!
		//如果返回false表示元素已经在视口中了!
        return fold <= $(element).offset().top - settings.threshold;
    };
rightoffold用于判断元素在水平方向上面时候已经出现在可视区域内了,如果是false那么表示元素已经在可视区域内了

 $.rightoffold = function(element, settings) {
        var fold;
        //如果container是window那么获取窗口的滚动距离+窗口的宽度!
        if (settings.container === undefined || settings.container === window) {
            fold = $window.width() + $window.scrollLeft();
        } else {
            fold = $(settings.container).offset().left + $(settings.container).width();
        }
        //offset表示元素距离左边的距离,如果距离大于窗口的滚动距离+窗口的宽度那么表示
		//元素还不再可视区域内,返回false表示元素出现在了可视区域内了!
        return fold <= $(element).offset().left - settings.threshold;
    };
上面两种情况表示元素应该触发appear事件了,但是如果是下面任何一种情况我们就不需要任何操作

第一种情况:元素已经在垂直方向上面滚动隐藏了

$.abovethetop = function(element, settings) {
        var fold;
        //如果是window对象那么获取在垂直方向上面滚动的距离
        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;
        //如果是window对象那么我们获取已经在水平方向上滚动的距离
        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();
    };

对于上面两种情况我们在检测的时候什么也不做

if ($.abovethetop(this, settings) ||
                    $.leftofbegin(this, settings)) {
                //如果垂直方向被滚动进去太多,或者水平方向滚动太多导致隐藏那么什么不做!
                        /* Nothing. */
                } 

问题12:threshold用于设置阀值,也就是元素快出现在视口中了,但是距离视口还有threshold距离时候就触发自定义appear事件?

解答:正确

其值默认是0

 var settings = {
            threshold       : 0,//阀值默认是0,表示出现在视口中才加载
            failure_limit   : 0,
            event           : "scroll",
            effect          : "show",
            container       : window,
            data_attribute  : "original",
            skip_invisible  : false,
            appear          : null,
            load            : null,
            placeholder     : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"
        };

如belowthefolder中用他做了一个判断

  return fold <= $(element).offset().top - settings.threshold;
//窗口滚动的距离+窗口高度和元素距离文档顶部的距离相比,如果减去threshold小于他,表示元素已经在特定阀值下出现在可视区域内了!

在rightoffolder中做了一个判断

 return fold <= $(element).offset().left - settings.threshold;
//如果元素距离左边的距离减去阀值已经大于窗口滚动的距离+窗口本身的宽度,那么表示元素已经在水平方向上出现在可视区域内了!

在abovethetop中做了一个判断元素是否已经在垂直方向上被滚动条卷进去了

 return fold >= $(element).offset().top + settings.threshold  + $(element).height();
//如果滚动的距离过大,已经大于元素距离文档的高度+阀值+元素本身的高度,表示元素垂直方向已经被隐藏了,也就是元素底部已经被卷进去了!

在leftofbegain中判断元素是否在水平方向上被滚动条卷进去了

 return fold >= $(element).offset().left + settings.threshold + $(element).width();
//如果窗口在水平方向滚动的距离过大,大于元素距离文档的距离+阀值+元素本身的宽度,那么表示元素右边都已经被卷进去了!

问题13:failurelimit的作用是什么?
 if (undefined !== options.failurelimit) {
                options.failure_limit = options.failurelimit;
                delete options.failurelimit;
            }
我们可以看到我们传入的failurelimit最后在内部被转化为failure_limit了,那么我们看看failure_limit用在什么地方了
if ($.abovethetop(this, settings) ||
                    $.leftofbegin(this, settings)) {
		//如果垂直方向被滚动进去太多,或者水平方向滚动太多导致隐藏,那么什么不做!
                        /* Nothing. */
                } else if (!$.belowthefold(this, settings) &&//表示元素出现在可视区域之内,我们触发自定义事件
                    !$.rightoffold(this, settings)) {
                        $this.trigger("appear");
                        /* if we found an image we'll load, reset the counter */
                        counter = 0;
                } else {//如果我们发现没有元素出现在可视区域之内,我们不会往下继续搜索,而是直接结束本次的搜索
                    if (++counter > settings.failure_limit) {
                        return false;
                    }
                }

注意:counter是update方法中的局部变量,本次搜索会对所有没有出现在视口中的元素进行循环,看看有没有元素出现在可视区域,如果有那么触发自定义事件appear。但是如果遍历了failure_limit个没有出现在可视区域的元素(也就是这些元素还没有被加载),那么我们结束本次循环,也就是不会循环遍历其它元素了。这是基于一个假设: 图片被认为是流式分布的, 图片在页面中的次序和 HTML 代码中次序相同. 但是在一些布局中, 这样的假设是不成立的,所以failure_limit就是用于解决这种不成立的情况。

 if (++counter > settings.failure_limit) {
                        return false;
                    }

通过这里你应该可以看到,如果遍历了没有出现在可视区域中的元素的个数已经超过了failurelimit个,那么我们就会跳出循环,也就是结束上面的each循环。默认failurelimit是0,那么表示如果当我们搜索的时候发现前面没有元素在可视区域内,那么我们结束本次循环,也就是不会继续搜索集合中后面的元素,但是这当页面的次序和HTML代码中的次序不同的时候就存在问题,所以我们可以把failurelimit设置大一点,表示如果前面的元素没有出现在可视区域之内,那么我们依然往下继续搜索failurelimit个元素(也就是继续遍历集合中后面的failurelimit个元素,看看他们有没有出现在集合中间)。(我觉得这当有浮动或者决定定位等情况时候是较好的,因为这时候DOM中排在后面的元素可能已经在视口中的,这时候前面的元素可能不需要显示但是后面的却需要显示了)

问题14:用户自己设置的参数的权重肯定比默认的权重要高,因为用的是$.extend方法?

解答:没错

  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);
        }
注意:因为我们使用了extend方法,所以用户自己设置的参数会覆盖默认的参数。

问题15:我们如何自定义自己的选择器?

我们来看看lazyload是如何自定义自己的伪类选择器的,这还是值得学习的

 $.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}); }
    });
我们看到,我们以后可以用$("img.below-the-fold")等用来判断元素在垂直方向上是否没有在可视区域内,而$("img.above-the-fold")判断元素是否在垂直方向上被卷进去隐藏了,$("img.left-of-fold")用于判断元素是否在左边被滚动条卷入隐藏,而$('img.right-of-fold')判断元素在水平方向上是否在可视区域内。

我们看看如何自定义了选择器,这一点我觉得很值得学习

jQuery.expr = Sizzle.selectors;
//自定义选择器
jQuery.expr[":"] = jQuery.expr.pseudos;
上面两句代码来自于jQuery源码,也就是说最终我们是扩展了Sizzle的伪类选择器Sizzle.selectors.pseudos。让Sizzle的伪类选择器具有上面的选择器
我们分析一下这几个伪类选择器:

below-the-fold判断元素在可视区域之下:

        "below-the-fold" : function(a) { return $.belowthefold(a, {threshold : 0}); }//不包含阀值
above-the-top用于判断元素被垂直方向卷进去了,也就是不再可视区域之下

        "above-the-top"  : function(a) { return !$.belowthefold(a, {threshold : 0}); },
right-of-fold判断元素的可视区域右边

        "right-of-fold"  : function(a) { return $.rightoffold(a, {threshold : 0}); },
left-of-fold判断元素左边被卷进去了

        "left-of-fold"   : function(a) { return !$.rightoffold(a, {threshold : 0}); }
in-viewport判断元素在可视区域之内

        "in-viewport"    : function(a) { return $.inviewport(a, {threshold : 0}); },
我们看看$.inviewport的源码

 $.inviewport = function(element, settings) {
         return !$.rightoffold(element, settings) && !$.leftofbegin(element, settings) &&
                !$.belowthefold(element, settings) && !$.abovethetop(element, settings);
     };
注意:表示元素不再可视区域左边,右边,上边,下面,那么一定在可视区域之内。伪类选择器的使用 阅读该博客,他实现了当页面加载完全以后我们在后台自动加载没有出现在可视区域的图片

right-of-screen和right-of-fold一样,left-of-screen和left-of-fold一样,above-the-fold和above-the-top一样

 "right-of-screen": function(a) { return $.rightoffold(a, {threshold : 0}); },
        "left-of-screen" : function(a) { return !$.rightoffold(a, {threshold : 0}); },
        /* Maintain BC for couple of versions. */
        "above-the-fold" : function(a) { return !$.belowthefold(a, {threshold : 0}); },
问题16:placeholder用于当我们没有为页面指定url时候,但是我们访问了页面,这时候就会加载placeholder对应的图片!
var settings = {
            threshold       : 0,
            failure_limit   : 0,
            event           : "scroll",
            effect          : "show",
            container       : window,
            data_attribute  : "original",
            skip_invisible  : false,
            appear          : null,
            load            : null,
            placeholder     : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"
        };
我们的placeholder属性有一个默认的DataURL的图片,当然也可以自己提供,我们看看placeholder有什么用

   /* If no src attribute given use data:uri. */
            if ($self.attr("src") === undefined || $self.attr("src") === false) {//如果设置了src那么会直接加载图片,而不用placeholder!
                if ($self.is("img")) {
                    $self.attr("src", settings.placeholder);
                }
            }
通过这里的代码你应该明白了,当我们的调用对象的所有的DOM元素如果src没有设定或者设置为false的时候,那么我们把他们的src设置为占位符,所以我们要懒加载图片的时候可以不要设置src属性为一个新的图片,因为该插件有默认的占位符地址!但是我们如果要设置为新的占位符,也可以覆盖默认的placeholder!
问题17:IOS5当点击了回退按钮的时候,我们要手动触发这些元素的自定义事件appear?

解答:没错

* 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) {//必须绑定到window!
                if (event.originalEvent && event.originalEvent.persisted) {
                    elements.each(function() {
                        $(this).trigger("appear");
                    });
                }
            });
        }
FF和Opera有一个特效叫做往返缓存,可以在用户使用浏览器的后退和前进按钮时候加快页面的转换速度,这个缓存不仅保存了页面数据还保存了DOM和JS的状态,实际上是整个页面都保存到了内存里面,如果页面保存在bfcache中,那么再次打开页面时就不会触发onload。但是为了形象说明bfcache行为,FF提供了一系列新事件。

pageShow:

(1)在压面显示时候触发,无论是否来自于bfcache,在重新加载页面(刷新)时候pageShow时间>load时间,也就是在load之后触发;而对于bfcache中页面,pageShow在页面状态完全恢复时候触发,同时必须将该事件绑定到window上!
(2)pageShow的event有一个persisted属性,如果页面被保存在了bfcache中,那么值为true!
pageHide:

(1)该事件在onunload事件之前触发,同时必须将他绑定到window对象上,该事件的event也有一个persisted参数,对于pageShow事件来说如果页面从bfcache中加载那么persisted为true,对于pageHide来说如果页面卸载后会被保存到bfcache中那么persisted就是true!
(2)支持pageShow,pageHide的浏览器有FF,SF5+,Chrome,Opera

(3)注意:指定了onunload的事件处理程序的页面会被自动排除在bfcache之外,即使事件是空的,因为onunload常用于销毁onload中执行的操作,而跳过onload后再次显示页面很可能导致页面不正常!
现在我们回到上面的代码:

jQuery.Event有一个originalEvent用于获取JS原生的事件对象,如果该对象的persisted为true,那么表示页面是从bfcache中加载的,这时候我们必须手动调用自定义事件appear,因为对于IOS5中他不会自动调用!
问题18:我们可以自定义事件吗,自定义的事件然后我们自己手动触发,这样的话是不是更加友好?

 /* When wanted event is triggered load original image */
            /* by triggering appear.                              */
            if (0 !== settings.event.indexOf("scroll")) {
                $self.bind(settings.event, function() {
                    if (!self.loaded) {
                        $self.trigger("appear");
                    }
                });
            }
通过上面的代码,你应该明白如果用户自定义了事件,如"qinliang",那么该插件除了给每一个元素绑定了appear事件以外,还通过上面这个代码绑定了我们自定义的事件"qinliang"。这时候当我们通过trigger触发了自定义的事件的时候,其实我们还是调用了appear事件,但是允许自定义事件却使得该插件具有扩展性!
$(document).ready(function()
{
   $("img.lazy").show().lazyload({
      placeholder : "img/grey.gif",
	  event : "qinliang",  /*我们自定义的事件,只有我们手动调用trigger触发这个事件的时候才会加载*/
	  effect:"fadeIn"    //默认使用show将元素显示出来
   });
   //这时候如果是垂直方向上面我们必须要同时触发img:below-the-fold和img:above-the-top
   //因为如果当我们滚动条已经到了中间的时候,如果没有above-the-top那么上面的图片不会加载
   //这种方式适用于当页面加载完成以后我们在后台默默加载图片的情况,这时候用定时器就可以了!  
 })
	$(window).load(function()
	{
	//这时候当load时候我们默默在后台加载图片!
	  setTimeout(function() {$("img:below-the-fold").trigger("qinliang")}, 10000); 
     setTimeout(function() {$("img:above-the-top").trigger("qinliang")}, 10000);  
	});
这种自定义事件用于在后台默默加载图片还是不错的选择!如果是水平方向上我们要手动触发left-of-fold和right-of-fold!但是,显然这种方式没有按需加载,如只加载位于可视区域的内容那么优秀。
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值