JS實現圖片懶加載插件


一、前言

 我在前幾篇博客的記錄中,有說自己在做一個圖片懶加載的功能,然后巴拉巴拉的遇到哪些問題,結果做完了也沒對懶加載這個功能做一些記錄,所以這篇文章主要針對我所實現的思路,以及代碼做個記錄,實現不佳之處還望見諒和指出。

二、實現原理與相關問題

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轉為數組對象,可以看這篇文章。

JS nodeList轉數組,兼容IE低版本

那么大概記錄這么多了,本人本地運行是沒問題的,有問題歡迎大家指出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM