一、前言
我在前幾篇博客的記錄中,有說自己在做一個圖片懶加載的功能,然后巴拉巴拉的遇到哪些問題,結果做完了也沒對懶加載這個功能做一些記錄,所以這篇文章主要針對我所實現的思路,以及代碼做個記錄,實現不佳之處還望見諒和指出。
二、實現原理與相關問題
1.做成一個組件還是service?
公司框架是angular,類似於圖片懶加載這類較通用的功能,肯定得保證復用性與可拓展性,同事建議做成組件,哪張圖片需要懶加載給這個圖片添加組件名;我心想,那repeat出來一百張圖,那豈不是瞬間瞬間100個組件同時運行,內存爆炸....聽起來不是很優化的感覺。想了下,還是做成service,哪里需要注入我的service,調用我提前提供好的方法即可。
基本功能流程圖如上,提前為需要懶加載的圖片統一添加lazyImg-src="真實圖片地址">屬性,頁面加載完成獲取所有有lazyImg-src屬性的dom,獲取dom操作只會執行一次,我不想每次滾動都會反復獲取dom,這樣太友好。
頁面加載完成會自調一次懶加載函數,之后就交給滾動事件觸發,當一個dom元素的圖片替換完成,就將此dom從數組中刪除,懶加載函數執行的前置條件為dom數組不為空,那么當第一次懶加載完后無論用戶怎么滾動,核心代碼不會再執行。
2.如何判斷圖片是否在視圖框內?
這個問題可以說是懶加載的核心問題,弄懂這個,功能已經做了一大半了,我們來看一個圖:
判斷一張圖是否在視圖范圍內,需要判斷X軸與Y軸的進入,離開的兩種情況,加起來就是四個條件,只要同時滿足,則元素一定在視圖范圍內。
3.怎么使用?
給需要懶加載的元素添加lazyImg-src屬性,里面存放真實的圖片地址,為了更好的體驗,你可以將圖片路徑設置為一張loading的圖片,在懶加載時再進行替換。
調用lazyload方法,並傳入視圖對象,什么意思呢,比如下面給出的demo代碼中,位置參照對象是window,視圖對象是ul,判斷的是每張img。
當觸發滾動事件時,監聽的是ul的滾動條,步驟如下,引入JS
先獲取視圖對象,例如:
const viewDom = document.querySelector('.container');
然后調用方法lazyload(viewDom )即可。
你也可以不傳遞視圖對象,那么此時視圖對象會默認為window,也就是說當觸發滾動事件時,監聽的是window的滾動條。
三、實現代碼
效果圖,此時的滾動事件監聽的是ul的滾動條。隨着滾動,netWork中可以看到img在一張張加載。
HTML部分,圖片自己准備,懶加載JS記得引入
<ul class="container"> <li><img src="img/timg.gif" alt="1" lazyImg-src="img/1.jpg"></li> <li><img src="img/timg.gif" alt="2" lazyImg-src="img/2.jpg"></li> <li><img src="img/timg.gif" alt="3" lazyImg-src="img/3.jpg"></li> <li><img src="img/timg.gif" alt="4" lazyImg-src="img/4.jpg"></li> <li><img src="img/timg.gif" alt="5" lazyImg-src="img/5.jpg"></li> <li><img src="img/timg.gif" alt="6" lazyImg-src="img/6.jpg"></li> <li><img src="img/timg.gif" alt="7" lazyImg-src="img/7.jpg"></li> <li><img src="img/timg.gif" alt="8" lazyImg-src="img/8.jpg"></li> <li><img src="img/timg.gif" alt="9" lazyImg-src="img/9.jpg"></li> <li><img src="img/timg.gif" alt="10" lazyImg-src="img/10.jpg"></li> </ul>
CSS部分:
body{ position:relative; padding:0; margin:0; display: table; } .container{ list-style: none; height: 400px; width: 250px; overflow: auto; margin-top:200px; } .container>li{ margin-bottom: 10px; } .container>li>img{ background-color: #e4393c; width: 200px; height: 200px; }
JS部分,別被變量的數量嚇到,搞懂原理其實真的不復雜,或者打斷點跟着跑一遍。
//處理圖片懶加載 function lazyload (option) { //如果不傳遞視圖對象,則默認視圖對象為window var viewDom = option || window, scroll_top, //滾動條Y軸距離 scroll_left, //滾動條X軸距離 viewDomWidth, //視圖寬 viewDomHeight,//視圖高 viewDomLeft, //視圖左偏移量 viewDomTop, //視圖上偏移量 imgWidth, //img寬 imgHeight, //img高 imgLeft, //img左偏移量 imgTop, //img上偏移量 imgArr = [], doc = document.documentElement; //獲取視圖元素寬高,左上偏移量,分為window或普通dom兩種情況 if (viewDom === window) { viewDomWidth = doc.clientWidth; viewDomHeight = doc.clientHeight; viewDomLeft = doc.offsetLeft; viewDomTop = doc.offsetTop; }else{ viewDomWidth = viewDom.offsetWidth; viewDomHeight = viewDom.offsetHeight; viewDomLeft = viewDom.offsetLeft; viewDomTop = viewDom.offsetTop; }; /** * @desc 將nodeList轉為數組,方便操作 * @param {object} data nodelist對象 */ function nodeListToArr(data) { var arr = []; try{ //ie8及以下不支 arr = Array.prototype.slice.call(data); }catch(e){ //兼容寫法 var len = data.length; for(var i = 0; i < len; i++){ arr.push(data[i]); } }; return arr; }; //取得所有需要懶加載的dom元素 var imgNode = document.querySelectorAll("[lazyImg-src]"); imgArr = nodeListToArr(imgNode); //替換路徑函數,懶加載核心函數 function replaceUrl () { //只有img數組不為空,才會執行替換路徑的操作 if(imgArr.length){ var i = 0; //獲取視圖元素滾動條滾動距離 if (viewDom === window) { scroll_top = doc.scrollTop; scroll_left = doc.scrollLeft; }else{ scroll_top = viewDom.scrollTop; scroll_left = viewDom.scrollLeft; }; //遍歷需要懶加載的dom節點 for (; i<imgArr.length; i++) { //獲取當前元素的寬高 imgWidth = imgArr[i].offsetWidth; imgHeight = imgArr[i].offsetHeight; //當前元素隨着滾動的變化的偏移量為 imgLeft = imgArr[i].offsetLeft - scroll_left; imgTop = imgArr[i].offsetTop - scroll_top; //橫向判斷是否進入視圖元素 var boundaryLeft = imgLeft + imgWidth - viewDomLeft; var boundaryRight = viewDomLeft + viewDomWidth - imgLeft; //同理縱向判斷是否進入視圖元素 var boundaryTop = imgTop + imgHeight - viewDomTop; var boundaryBottom = viewDomTop + viewDomHeight - imgTop; if(boundaryLeft>0 && boundaryRight>0 && boundaryTop>0 && boundaryBottom>0) { //替換圖片路徑,添加判斷,img替換src,非img替換background if(imgArr[i].nodeName === "IMG"){ imgArr[i].src = imgArr[i].attributes['lazyimg-src'].value; }else{ //不是圖片的替換背景圖 // imgArr[i].style.backgroundImage = imgArr[i].attributes['lazyimg-src'].value; }; //操作完一項就刪除一項,同時重置i; imgArr.splice(i,1); i -= 1; }; }; }; }; //自調 replaceUrl(); //添加事件監聽 viewDom.addEventListener("scroll",replaceUrl); }; //測試調用,默認綁定window,或傳遞你需要監聽的dom對象,調用就2步 var container = document.querySelector(".container"); lazyload(container);
四、遇到的一些問題
1.判斷元素是否在視圖內,就Y軸而言,隨着滾動條向下滾動,可以說img元素距離參照物上端會越來越近,也就說這個img上偏移量會越來越小,然后我果斷用了JS的offsetTop屬性來獲取上偏移量,結果踩了個坑,但用JQ的offset().top卻沒問題,原因是offsetTop獲取的是img初始位置的上偏移量,不隨滾動條滾動變化,而offset().top是變化的,具體想知道JS offsetTop與offset().top不同以及怎么通過JS獲取可變的上偏移量,可以閱讀博主這篇文章。
JQ的offset().top與js的offsetTop區別詳解
2.此實現方法需要一開始獲取所有需要懶加載的dom元素,而公司用的angular,angular有自己的生命周期,怎么保證我獲取dom的時候,所有angular模板以及指令已完全渲染完成呢?有興趣可以閱讀博主這篇文章:
angular監聽dom渲染完成,判斷ng-repeat循環完成
3.通過querySelector獲取的dom其實是一個domList對象,然而我在處理完每個dom后想要刪除此項,而domList對象不支持數組方法,怎么把domList轉為數組對象,可以看這篇文章。
那么大概記錄這么多了,本人本地運行是沒問題的,有問題歡迎大家指出。