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);
//最初進入頁面從所有元素中篩選出在視區(qū)內(nèi)的元素執(zhí)行任務
bindEvents();
//滾動事件停止后視區(qū)內(nèi)的元素執(zhí)行任務
$(window).on("scroll", function() {
if (timer) { clearTimeout(timer); }
timer = setTimeout(bindEvents, 1000);
})
function bindEvents() {
elements.each(function() {
var me = this;
me.loaded = false; //將所有元素標記為 未執(zhí)行任務,執(zhí)行過appear()的元素該屬性就會標記為true
if (checkIsInView(me)) {
appear(me);
}
});
//將元素列表的更新放到一屏結束后統(tǒng)一更新一次
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、模式的轉(zhuǎn)變
最開始使用了構造函數(shù)加原型的混合模式,但是后來發(fā)現(xiàn)這樣寫很有問題(當然很有可能是我掌握不精所致),直接?把這個完整的方法賦給每個元素,不容易將執(zhí)行完任務的元素從待執(zhí)行任務的元素列表中移除,所以后來改成了單例模式,然后這個就越看越像是jquery.lazyload.js的極簡版。
那么總結下思路的轉(zhuǎn)變過程:
滾動停止后對所有元素都去判斷是否在視區(qū),執(zhí)行相應任務——改為——滾動停止后從待執(zhí)行任務的元素列表中搜索留在視區(qū)內(nèi)的元素,顯示。
另外,在模式轉(zhuǎn)變的過程中函數(shù)方法的定義方式也要發(fā)生改變,這使得我對原型函數(shù)中的對象方法有了進一步的認識。我們都知道在js中一切都是對象,對象方法和對象屬性沒什么不同,只是前者的值是函數(shù)而已,所以原型函數(shù)中的對象方法看起來也都是鍵值對的樣子。想訪問該屬性,當做字符串訪問,想調(diào)用該方法加(),如:
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、更新元素列表這項工作放到哪兒執(zhí)行更好?
每個元素的圖片被替換之后就立即更新列表,還是這一屏的元素的圖片都替換完了之后再統(tǒng)一更新一次?這兩者哪個耗時更短?前者是篩選次數(shù)執(zhí)行的多,后者是查找的次數(shù)執(zhí)行的多,個人從理論上分析,篩選是要更耗時的,因為一次篩選過程要查找然后更新新數(shù)組(不管其內(nèi)部用的是數(shù)組還是指針都要多幾步的,那么對于數(shù)組元素的刪除、過濾其內(nèi)部是如何實現(xiàn)的還有待考究)。所以我是在一屏的圖片被替換后更新一次元素列表。
這是更新數(shù)組的代碼:
var temp=$.grep(elements,function(element){
return (!element.loaded);//最開始遍歷元素的時候給每個元素設置一個屬性loaded作為標志位
})
elements=temp;
3、添加限制條件:滾動停止后才執(zhí)行替換圖片的任務
這一知識也是聽師父介紹的,這個在淘寶無線端有應用。這兒利用setTimeout來判斷滾動事件停止,滾動過程中不去加載。(像移動端有區(qū)分touchStart,touchEnd事件就不用這么麻煩了)
$(window).on("scroll", function() {
if (timer) { clearTimeout(timer); }
timer = setTimeout(bindEvents, 1000);
})
最開始的時候把timer定義在了scroll事件里,然后就理所當然的發(fā)生里一些不可思議的事情。。。真是粗心大意害死人。
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;
}