1、前言html
我在前幾篇博客的記錄中,有說本身在作一個圖片懶加載的功能,而後巴拉巴拉的遇到哪些問題,結果作完了也沒對懶加載這個功能作一些記錄,因此這篇文章主要針對我所實現的思路,以及代碼作個記錄,實現不佳之處還望見諒和指出。node
2、實現原理與相關問題數組
1.作成一個組件仍是service?框架
公司框架是angular,相似於圖片懶加載這類較通用的功能,確定得保證複用性與可拓展性,同事建議作成組件,哪張圖片須要懶加載給這個圖片添加組件名;我心想,那repeat出來一百張圖,那豈不是瞬間瞬間100個組件同時運行,內存爆炸....聽起來不是很優化的感受。想了下,仍是作成service,哪裏須要注入個人service,調用我提早提供好的方法便可。dom
基本功能流程圖如上,提早爲須要懶加載的圖片統一添加lazyImg-src="真實圖片地址">屬性,頁面加載完成獲取全部有lazyImg-src屬性的dom,獲取dom操做只會執行一次,我不想每次滾動都會反覆獲取dom,這樣太友好。函數
頁面加載完成會自調一次懶加載函數,以後就交給滾動事件觸發,當一個dom元素的圖片替換完成,就將此dom從數組中刪除,懶加載函數執行的前置條件爲dom數組不爲空,那麼當第一次懶加載完後不管用戶怎麼滾動,核心代碼不會再執行。測試
2.如何判斷圖片是否在視圖框內?優化
這個問題能夠說是懶加載的核心問題,弄懂這個,功能已經作了一大半了,咱們來看一個圖:spa
判斷一張圖是否在視圖範圍內,須要判斷X軸與Y軸的進入,離開的兩種狀況,加起來就是四個條件,只要同時知足,則元素必定在視圖範圍內。prototype
3.怎麼使用?
給須要懶加載的元素添加lazyImg-src屬性,裏面存放真實的圖片地址,爲了更好的體驗,你能夠將圖片路徑設置爲一張loading的圖片,在懶加載時再進行替換。
調用lazyload方法,並傳入視圖對象,什麼意思呢,好比下面給出的demo代碼中,位置參照對象是window,視圖對象是ul,判斷的是每張img。
當觸發滾動事件時,監聽的是ul的滾動條,步驟以下,引入JS
先獲取視圖對象,例如:
const viewDom = document.querySelector('.container');
而後調用方法lazyload(viewDom )便可。
你也能夠不傳遞視圖對象,那麼此時視圖對象會默認爲window,也就是說當觸發滾動事件時,監聽的是window的滾動條。
3、實現代碼
效果圖,此時的滾動事件監聽的是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);
4、遇到的一些問題
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轉爲數組對象,能夠看這篇文章。
那麼大概記錄這麼多了,本人本地運行是沒問題的,有問題歡迎你們指出。