Talk is cheap,show me the Code~先上代码,再说心路
/**
* lazyload demo
*
*/
(function($, window, document, undefined) {
$.fn.Lazyload = function(opts) {
var elements = this,
timer = false,
defaults = {
data_attribute: 'data-src',
threshold: 0
},
options = $.extend({}, defaults, opts);
//最初进入页面从所有元素中筛选出在视区内的元素执行任务
bindEvents();
//滚动事件停止后视区内的元素执行任务
$(window).on("scroll", function() {
if (timer) { clearTimeout(timer); }
timer = setTimeout(bindEvents, 1000);
})
function bindEvents() {
elements.each(function() {
var me = this;
me.loaded = false; //将所有元素标记为 未执行任务,执行过appear()的元素该属性就会标记为true
if (checkIsInView(me)) {
appear(me);
}
});
//将元素列表的更新放到一屏结束后统一更新一次
var temp = $.grep(elements, function(element) {
return (!element.loaded);
})
elements = $(temp);
};
function checkIsInView(element) {
var isAboveTheView,
isBelowTheView,
$window = $(window);
isAboveTheView = ($(element).offset().top + $(element).height() + options.threshold < $window.scrollTop()) ? true : false;
isBelowTheView = ($(element).offset().top - options.threshold > $window.scrollTop() + $window.height()) ? true : false;
if (!isBelowTheView && !isAboveTheView) {
return true;
} else {
return false;
}
};
function appear(element) {
var data_src = $(element).attr(options.data_attribute);
$(element).attr("src", data_src);
element.loaded = true;
};
}
})(window.jQuery, window, document);
心路历程&查漏补缺
1、模式的转变
最开始使用了构造函数加原型的混合模式,但是后来发现这样写很有问题(当然很有可能是我掌握不精所致),直接�把这个完整的方法赋给每个元素,不容易将执行完任务的元素从待执行任务的元素列表中移除,所以后来改成了单例模式,然后这个就越看越像是jquery.lazyload.js的极简版。
那么总结下思路的转变过程:
滚动停止后对所有元素都去判断是否在视区,执行相应任务——改为——滚动停止后从待执行任务的元素列表中搜索留在视区内的元素,显示。
另外,在模式转变的过程中函数方法的定义方式也要发生改变,这使得我对原型函数中的对象方法有了进一步的认识。我们都知道在js中一切都是对象,对象方法和对象属性没什么不同,只是前者的值是函数而已,所以原型函数中的对象方法看起来也都是键值对的样子。想访问该属性,当做字符串访问,想调用该方法加(),如:
var person = {
firstName: "John",
lastName : "Doe",
id : 5566,
fullName : function() {
return this.firstName + " " + this.lastName;
}
};
console.log(person.fullName);//function() { return this.firstName + " " + this.lastName;}
console.log(person.fullName());//John Doe
2、更新元素列表这项工作放到哪儿执行更好?
每个元素的图片被替换之后就立即更新列表,还是这一屏的元素的图片都替换完了之后再统一更新一次?这两者哪个耗时更短?前者是筛选次数执行的多,后者是查找的次数执行的多,个人从理论上分析,筛选是要更耗时的,因为一次筛选过程要查找然后更新新数组(不管其内部用的是数组还是指针都要多几步的,那么对于数组元素的删除、过滤其内部是如何实现的还有待考究)。所以我是在一屏的图片被替换后更新一次元素列表。
这是更新数组的代码:
var temp=$.grep(elements,function(element){
return (!element.loaded);//最开始遍历元素的时候给每个元素设置一个属性loaded作为标志位
})
elements=temp;
3、添加限制条件:滚动停止后才执行替换图片的任务
这一知识也是听师父介绍的,这个在淘宝无线端有应用。这儿利用setTimeout来判断滚动事件停止,滚动过程中不去加载。(像移动端有区分touchStart,touchEnd事件就不用这么麻烦了)
$(window).on("scroll", function() {
if (timer) { clearTimeout(timer); }
timer = setTimeout(bindEvents, 1000);
})
最开始的时候把timer定义在了scroll事件里,然后就理所当然的发生里一些不可思议的事情。。。真是粗心大意害死人。
4、获取到文档顶部距离
jquery中获取元素到文档顶部的距离:$().offset().top
当前视口到文档顶部的距离: scrollTop()
原生javascript中只能获取相对父元素的left,top,那么是如何获得距离文档顶部的距离的?
一层层递归到父元素为body
function getOffsetTop(element){
var actualTop = element.offsetTop;
var current = element.offsetParent;
while (current !== null){
actualTop += current.offsetTop;
current = current.offsetParent;
}
return actualTop;
}