之前對預(yù)加載的實(shí)現(xiàn)方案做了介紹,這一篇文章主要是我對圖片的懶加載的實(shí)現(xiàn)的一個(gè)總結(jié)。主要包括:
- 視區(qū)檢測
- 圖片懶加載及延遲顯示
實(shí)例簡介
之前一直對單頁應(yīng)用有興趣,所以自己寫了一個(gè)前端路由,相關(guān)的文章見這里,這個(gè)單頁應(yīng)用采取hash的方式實(shí)現(xiàn)路由。最終的實(shí)例頁面見這里。倉庫在這里是一個(gè)經(jīng)典的單頁應(yīng)用。要做優(yōu)化的就是主頁的信息滾動。這些信息通過ajax從服務(wù)器端獲取,這里為了方便,服務(wù)器端會一直返回?cái)?shù)據(jù),哪怕是重復(fù)的。頁面如下:

懶加載
可以看到,頁面中,每一條消息都有一個(gè)圖片,這個(gè)時(shí)候,如果在dom剛剛建立好,就對所有的圖片進(jìn)行加載,此時(shí),占用過多的下載通道(我的是每次顯示10條消息,接近頁面底部時(shí)會預(yù)加載),會導(dǎo)致后面的信息加載速度變慢,用戶體驗(yàn)不好。而圖片懶加載是指:圖片進(jìn)入用戶視野才會進(jìn)行加載,而不是在dom樹一構(gòu)建好就進(jìn)行加載。 道理很簡單,但是我在實(shí)現(xiàn)的過程中還是碰到了一些問題,下面就是我的實(shí)現(xiàn)方案。
懶加載實(shí)現(xiàn)方案
總體變量以及函數(shù)定義
//記錄圖片的序號
let num = 0;
//記錄是否正在獲取數(shù)據(jù),保證請求只做一次
let state = true;
//記錄圖片數(shù)據(jù),index,src,height三個(gè)關(guān)鍵元素
var img_data =[];
//記錄表單的距離頁面頂端的距離
var list_height = 0;
function getH(obj) {
//獲得對象距離頁面頂端的距離
}
function lazy_load(){
//圖片懶加載的實(shí)現(xiàn)函數(shù)
}
function getInfo(){
//從服務(wù)器端獲取商家發(fā)布的新信息
//并向圖片數(shù)據(jù)中存放圖片信息
}
function main(){
//主函數(shù)
//實(shí)現(xiàn)初始化
//滾動事件的綁定等
}
獲取元素相對頁面頂部的高度
這個(gè)函數(shù)其實(shí)不難,主要涉及到目標(biāo)元素下面幾個(gè)屬性:
- node.offsetTop:相對其父元素的位置
- node.offsetParent: 元素的父元素
所以,要獲取元素相對頁面頂部的高度,其實(shí)只需要進(jìn)行遞歸或者迭代就能實(shí)現(xiàn),這里采用迭代實(shí)現(xiàn):
function getH(obj) {
var h = 0;
while (obj) {
h += obj.offsetTop;
obj = obj.offsetParent;
}
return h;
}
數(shù)據(jù)的緩存
程序中,通過Ajax從服務(wù)器獲取數(shù)據(jù),每次最多獲取10條,在dom中,img標(biāo)簽最開始并不指定src,src存儲在ajax獲取到的信息中,我將其存入:img_data中,與它一同存入的,還有該圖片的高度height,第幾條信息index。
這里的height,可以采用上面的迭代得到,但是每次迭代對資源損耗比較大,事實(shí)上也是沒有必要的,因?yàn)槊織l信息是固定的高度,所以根據(jù)其是第幾條信息,再獲取一個(gè)list相對頁面頂部的高度,就能得到圖片相對頁面頂部的高度。我這里每個(gè)圖片(100px)算上間隙(40px)就是140px,只需要獲取整個(gè)列表相對頂部的高度,就能得到每個(gè)圖片相對頁面頂部的距離。
程序中大概像這樣子:
img_data.push({
index:(num),
height:list_height+(140)*(num),
src:data.src,
loaded:false //定期清理,加載之后的圖片信息進(jìn)行清除,降低內(nèi)存使用
})
視區(qū)的檢測
圖片是否落在用戶視區(qū),需要用到以下高度:
- height1:document.body.scrollTop:瀏覽器滾動的高度
- height2:document.body.clientHeight:可視區(qū)域的高度
- height3:node.height:也就是之前獲取到的元素相對頁面頂部的高度(并不是相對可視區(qū)域的頂部)
當(dāng)height3>heihgt1且height3<height2+height1的時(shí)候,可以認(rèn)為這個(gè)元素是出現(xiàn)在用戶視區(qū)的,從而將img_data的src賦值給這個(gè)塊的img標(biāo)簽,當(dāng)圖片加載好之后,opacity配合transition實(shí)現(xiàn)動態(tài)的浮現(xiàn)(據(jù)說,人感覺這樣加載的速度更快)。這一塊大致的代碼如下:
function lazy_load(){
var height1 = document.body.scrollTop+document.body.clientHeight;
img_data.forEach(function(item){
if(!item.loaded && item.height>document.body.scrollTop-100 && item.height < height1){
var img = document.querySelector("img[img-index='"+item.index+"']");
//選擇該圖片
if(img){
img.src = item.src;
item.loaded = true; //下面對img_data進(jìn)行filter的函數(shù),減少內(nèi)存消耗
img.onload = function(){
img.style.opacity = 1;//配合transition可以實(shí)現(xiàn)一個(gè)漸入的效果
}
img.onerror = function(){
img.style.opacity = 1;
img.src = '/failed.jpg';//加載失敗,
}
}
}
img_data = img_data.filter(function(item){
return !item.loaded;
})
}
滾動函數(shù)的綁定
直接將上述函數(shù)和window.onscroll進(jìn)行綁定是不太理想的,因?yàn)闈L動函數(shù)的觸發(fā)頻率很高,而視區(qū)的檢測如果每次滾動都進(jìn)行檢測,那么,一方面造成性能上的損失,一方面,似乎所有的圖片都能被檢測到出現(xiàn)在了視區(qū),從而導(dǎo)致所有的圖片都會被加載,并沒有起到懶加載的作用。所以在這里,我使用了函數(shù)消抖,原理也不難,網(wǎng)上的實(shí)現(xiàn)很多,這里給出我的實(shí)現(xiàn):
method.debounce = function(func,delay){
var timer;
return function(){
var args = arguments;
var context = this;
clearTimeout(timer);
timer = setTimeout(function(){
func.apply(this,args);
},delay);
}
}
和上述lazy_load結(jié)合,進(jìn)行綁定,代碼如下:
var lazy_event = method.debounce(lazy_load,500);//此處500ms可以適當(dāng)縮小
method.addevent(window,'scroll',lazy_event);
和消抖函數(shù)結(jié)合之后,用戶的滾動不會觸發(fā)lazy_load,只有當(dāng)用戶停止?jié)L動才會執(zhí)行l(wèi)azy_load,從而達(dá)到圖片懶加載的效果。
總結(jié)
這次無限滾動,我實(shí)現(xiàn)了兩種方案:預(yù)加載與圖片懶加載,配合消抖和節(jié)流以及緩存,能夠很好的提升頁面性能。希望面試的時(shí)候能用上吧。