JS實現圖片懶加載插件

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轉爲數組對象,能夠看這篇文章。

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

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

相關文章
相關標籤/搜索