jQuery.lazyload源码分析

前言

现在这个年代还在写jQuery,我承认有点跟不上时代。但为什么说jQuery过时呢?个人感觉这篇剖析的比较浅显易懂:《jQuery真的过时了!》。但其实jQuery和其相关的插件中还是有很多实用技巧的,本文对jQuery.lazyload-1.10.0.js的源码进行分析,了解jQuery的注册事件。

源码

废话不多说,直接源码

<!-- index.html -->
<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元素
                    $("<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);

$(’<img />’).one(‘load’)的触发机制

在代码115行左右又一段$(’<img />’).one(‘load’)的事件注册代码。当此事件触发后才会真正的把图片加载进来。
但$(’<img />’)是一个新元素,此元素也从未插入到DOM中,那么是什么时候被浏览器加载的呢?
这里有个知识点需要注意:当给一个img元素赋值src,且地址中的图片确实能被访问的时候就会触发此img的load事件
所以在代码中可以看到在.one()后面执行了.attr()方法,将实际的图片地址赋值给src,从而触发load事件。同时,还进行了实际图片地址可访问的检测。

delete操作符

传送门:delete 操作符 - JavaScript | MDN

知识技能获取,感谢[网易云课堂 - 微专业 - 前端高级开发工程师]运营团队。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值