lazyload 懒加载 详解
突然想了解jquery-lazyload的原理,就巴拉巴拉了代码。
帮助更好的了解 lazyload.js vue-lazyload
源码设计思路如下:
-
初始化配置默认值
-
配置默认值和传入的配置进行按需合并
-
设置滚动监听对象
-
遍历渲染图片元素
-
每个元素定义加载状态为false
-
如果元素是img,并且没有绑定src, 则赋值src为占位loading提示图片
-
给图片元素绑定一次appear渲染完成事件。
- 如果配置了渲染前执行函数,执行渲染前执行函数
- 新创建一个img元素,并绑定加载完成事件load
- 然后给新建img元素设置src为图片元素的图片链接地址,加载完成函数load触发
- 再次获取图片链接地址,隐藏图片元素,设置图片元素src为图片链接地址,使用配置动画显示图片元素
- 修改加载状态为true,筛选删除掉加载过的图片元素,获取新的元素遍历列表
- 如果配置了渲染后执行函数,执行渲染后执行函数
(原理:利用动态获取data-值得,动态赋值src实现图片懒加载)
-
如果设置得触发事件不是滚动事件,则事件绑定在图片元素上面
-
创造滚动监听方法,
-
如果监听方法是滚动,绑定给监听元素,窗口大小改变事件,文档加载完成事件
-
滚动监听方法
-
遍历渲染图片元素
-
如果图片元素存在隐藏,根据配置判断是否加载隐藏得元素图片
-
通过滚动距离逻辑判断是否执行 4.3 步骤绑定得事件
-
滚动距离监听逻辑(竖向滚动和横向滚动距离逻辑完全一样)
-
竖向滚动距离 大于 (图片到窗口顶部得距离 + 额外加载距离 + 元素本身高度) 时,不进行渲染
此时元素初始化已经滚动到上面去了,并没有在页面可视区域内
-
竖向滚动距离 小于 (图片到窗口顶部的距离 -额外加载距离) 时,不进行渲染
此时元素初始化已经滚动到下面去了,并没有在页面可视区域内
-
-
额外距离:可以立即为给图片上方多加一截高度。
-
如果图片元素在可视区域上面,然后图片元素又设置了一些定位等属性导致元素出现高度偏差,可以设置每次加载数量,解决这个问题。
-
扩展jquery的方法
源码已经中文注释解析:(源码仅有部分结构顺序的改动,帮助代码理解)
/*!
* Lazy Load - jQuery plugin for lazy loading images
*
* Copyright (c) 2007-2015 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.7
*
*/
(function ($, window, document, undefined) {
var $window = $(window);
// 扩展lazyload方法
$.fn.lazyload = function (options) {
// 初始化变量和默认值
var elements = this; // 获取到的全部img集合
var $container; // 监听对象
var settings = {
threshold: 0,
failure_limit: 0,
/*
触发的事件,默认scroll.
如果设置的事件是scroll,则绑定到监听对象上(container)
否则,设置到给一个img元素上
*/
event: "scroll", // 图片加载动画
// effect_speed: null, // 加载动画执行事件, 默认没有设置
effect: "show",
container: window, // 滚动监听对象,默认window
data_attribute: "original", // 设置获取src路径的data域名
skip_invisible: false, // 如果元素以及设置display:none, 那么是否还继续加载图片。默认false继续加载
appear: null, // 每张图片加载前回调
load: null, // 每张图片加载后回调
// 加载loading时,占位的loading图片
placeholder:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC",
};
// 把传入配置与初始化配置进行合并
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);
}
// 设置滚动监听对象,没有设置默认监听windo
/* Cache container as jQuery as object. */
$container =
settings.container === undefined || settings.container === window
? $window
: $(settings.container);
// 遍历获取到的img元素集合
elements.each(function () {
// 每个img元素
var self = this;
var $self = $(self);
// 设置img的加载状态为false
self.loaded = false;
// 设置loading图片
/*
如果直接设置img的scr,就会立即获取图片。
但是设置到data-属性,就可以等待到触发条件后,在设置到src属性里面,完成加载
*/
/* If no src attribute given use data:uri. */
if ($self.attr("src") === undefined || $self.attr("src") === false) {
// 进行一次img元素检测
if ($self.is("img")) {
// 设置所有没有设置src的img,都是渲染图片。
$self.attr("src", settings.placeholder);
}
}
// 定义appear加载完成事件,并给每个图片都绑定一次
/* When appear is triggered load original image. */
$self.one("appear", function () {
if (!this.loaded) {
// 如果设置了加载前函数,调用设置的加载前函数
if (settings.appear) {
var elements_left = elements.length;
settings.appear.call(self, elements_left, settings);
}
// 创建一个img,给这个img绑定bing函数,并获取data-的值赋值给src
// 利用新的img标签的渲染逻辑,渲染加载图片的
$("<img />")
.bind("load", function () {
// 再次获取data-的预设url
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);
// 设置img的加载状态为true,下一次就不再加载了
self.loaded = true;
// 过滤掉加载完成的图片,重新赋值加载img元素列表
/* 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));
}
});
// 如果设置的不是scroll事件,把设置的事件绑定到img元素上
/* 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) {
// 手动触发appear事件
$self.trigger("appear");
}
});
}
});
// 兼容iphone
/* 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");
});
}
});
}
// 窗口滚动, 改变。文档加载完成后执行的函数
function update() {
var counter = 0;
// 遍历img元素
elements.each(function () {
var $this = $(this);
// 如果元素已经设置隐藏了,然后根据配置项判断是否不进行加载。默认false,进行加载
// 如果是非滚动事件,则无法操作隐藏的元素事件,所以不需要在那边进行判断
if (settings.skip_invisible && !$this.is(":visible")) {
return;
}
console.log(this, "------------each");
// 判断是否到达显示位置
/*
滚动距离 大于 (图片元素距离顶部 + 设置的显示距离 + 元素自身距离) 不进行渲染
滚动距离 小于 (图片元素距离顶部 + 设置的显示距离) 不进行渲染
如果存在定位等印象渲染数量效果的情况,可以设置最大渲染数量解决
*/
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;
}
}
});
}
// 如果设置的事件是scroll, 给监听对象绑定scroll方法
/* Fire one scroll event per scroll. Not one scroll event per image. */
if (0 === settings.event.indexOf("scroll")) {
$container.bind(settings.event, function () {
return update();
});
}
// 监听窗口大小改变
/* Check if something appears when window is resized. */
$window.bind("resize", function () {
update();
});
/* 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;
// 获取滚动监听对象的滚动距离,并赋值给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);