從零實現(xiàn)瀑布流--圖片懶加載及其底層原理

文章不短(我寫的很詳細(xì)) 耐心看完 給你一個極好性能的瀑布流及圖片懶加載

瀑布流效果
瀑布流
何為瀑布流颂鸿?

瀑布流,又稱瀑布流式。是比較流行的一種網(wǎng)站頁面布局问顷,視覺表現(xiàn)為參差不齊的多欄布局,隨著頁面滾動條向下滾動禀梳,這種布局還會不斷加載數(shù)據(jù)塊并附加至當(dāng)前尾部杜窄。最早采用此布局的網(wǎng)站是Pinterest,逐漸在國內(nèi)流行開來算途。國內(nèi)大多數(shù)清新站基本為這類風(fēng)格塞耕。

即多行等寬元素排列,后面的元素依次添加到其后嘴瓤,等寬不等高扫外,根據(jù)圖片原比例縮放直至寬度達(dá)到我們的要求,依次按照規(guī)則放入指定位置廓脆。

那么規(guī)則是什么呢筛谚?
下面通過圖解來分析一下瀑布流的算法。

圖解瀑布流算法

當(dāng)?shù)谝慌排艥M足夠多的等寬圖片時(如下圖情況)停忿,自然而然的考慮到之后放置的圖片會往下面排放驾讲。

圖一

那么第六張圖片,放置在什么位置呢?是下圖的位置么蝎毡?

圖二

答案是:不是厚柳,那我們應(yīng)該怎樣排列呢?
為了減小每列的差距我們應(yīng)該將后面六張圖片中最高的那一個放到當(dāng)前這六列中最矮的那一列沐兵,正數(shù)第二高的圖片放到倒數(shù)第二矮的那一列......以此類推

圖三
  • 好了别垮,實現(xiàn)的思想大家已經(jīng)看了,接下來咱們用代碼實現(xiàn)一個三列的瀑布流
實現(xiàn)代碼
  1. 先看HTML扎谎、CSS
  • HTML
<body>
  <div class="container clearfix">
    <div class="column">
      <!-- <div class="card">
                <a href="#">
                    <div class="lazyImageBox">
                        <img src="" alt="" data-image="images/1.jpg">
                    </div>
                    <p>泰勒·斯威夫特(Taylor Swift)碳想,1989年12月13日出生于美國賓州,美國歌手毁靶、演員胧奔。2006年出道,同年發(fā)行專輯《泰勒·斯威夫特》预吆,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證</p>
                </a>
            </div> -->
    </div>
    <div class="column"></div>
    <div class="column"></div>
  </div>
</body>
  • CSS
html,
body {
    background: #D6D7DB;
}

.container {
    box-sizing: border-box;
    margin: 20px auto;
    width: 760px;
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
}

.container .column {
    box-sizing: border-box;
    width: 240px;
}

.card {
    margin-bottom: 10px;
    padding: 5px;
    background: #FFF;
    box-shadow: 3px 3px 10px 0 #222;
}

.card a {
    display: block;
}

.card a .lazyImageBox {
    overflow: hidden;
}

.card a .lazyImageBox img {
    width: 100%;
}

.card a p {
    margin-top: 5px;
    color: #000;
    font-size: 12px;
    line-height: 20px;
}
  • 創(chuàng)造了一個寬度為760px的容器 使用css3彈性盒使內(nèi)容兩端對齊然后將內(nèi)容分為3列龙填,每列240 像素
  1. 獲取數(shù)據(jù)
  • 模擬數(shù)據(jù)(創(chuàng)建data.json)
<data.json>
[
  {
    "id": 1,
    "pic": "images/9.jpg",
    "width": 300,
    "height": 433,
    "title": "泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美國賓州拐叉,美國歌手岩遗、演員。2006年出道凤瘦,同年發(fā)行專輯《泰勒·斯威夫特》宿礁,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
    "link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
  },
  {
    "id": 2,
    "pic": "images/5.jpg",
    "width": 300,
    "height": 200,
    "title": "泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美國賓州蔬芥,美國歌手梆靖、演員。2006年出道笔诵,同年發(fā)行專輯《泰勒·斯威夫特》返吻,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
    "link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
  },
  {
    "id": 3,
    "pic": "images/3.jpg",
    "width": 300,
    "height": 170,
    "title": "泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美國賓州乎婿,美國歌手思喊、演員。2006年出道次酌,同年發(fā)行專輯《泰勒·斯威夫特》恨课,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
    "link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
  },
  {
    "id": 4,
    "pic": "images/2.jpg",
    "width": 300,
    "height": 300,
    "title": "泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美國賓州岳服,美國歌手剂公、演員。2006年出道吊宋,同年發(fā)行專輯《泰勒·斯威夫特》纲辽,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
    "link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
  },
  {
    "id": 5,
    "pic": "images/3.jpg",
    "width": 300,
    "height": 170,
    "title": "泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美國賓州,美國歌手拖吼、演員鳞上。2006年出道,同年發(fā)行專輯《泰勒·斯威夫特》吊档,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
    "link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
  },
  {
    "id": 6,
    "pic": "images/10.jpg",
    "width": 300,
    "height": 257,
    "title": "泰勒·斯威夫特(Taylor Swift)篙议,1989年12月13日出生于美國賓州,美國歌手怠硼、演員鬼贱。2006年出道,同年發(fā)行專輯《泰勒·斯威夫特》香璃,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
    "link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
  },
  {
    "id": 7,
    "pic": "images/6.jpg",
    "width": 300,
    "height": 400,
    "title": "泰勒·斯威夫特(Taylor Swift)这难,1989年12月13日出生于美國賓州,美國歌手葡秒、演員姻乓。2006年出道,同年發(fā)行專輯《泰勒·斯威夫特》眯牧,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
    "link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
  },
  {
    "id": 8,
    "pic": "images/5.jpg",
    "width": 300,
    "height": 200,
    "title": "泰勒·斯威夫特(Taylor Swift)蹋岩,1989年12月13日出生于美國賓州,美國歌手炸站、演員星澳。2006年出道疚顷,同年發(fā)行專輯《泰勒·斯威夫特》旱易,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
    "link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
  }
]
  • 創(chuàng)建utils.js
  • 簡單封裝ajax
<utils.js>
class utils {
  constructor() {
    console.log('工具類');
  }
  /**
   * 封裝Ajax
   * @param {*} url 
   * @returns {Promise}
   */
  ajax (url) {
    return new Promise(resolve => {
      let xhr = new XMLHttpRequest;
      xhr.open('get', url);
      xhr.onreadystatechange = function () {
        (xhr.readyState === 4 && xhr.status === 200) && resolve(JSON.parse(xhr.responseText));
      };
      xhr.send();
    });
  }
}
  • 使用ajax獲取數(shù)據(jù)并渲染頁面 注意看注釋
  let data = await utils.ajax('./data.json');
  bindHTML(data);
  // 獲取到所有class為column的元素并將類數(shù)組轉(zhuǎn)化為數(shù)組實例
  let columns = Array.from(document.querySelectorAll('.column'));
  /**
   * 數(shù)據(jù)綁定
   * @param {Array} data 
   */
  function bindHTML (data) {
    // 根據(jù)服務(wù)器返回的圖片的寬高,動態(tài)計算出圖片放在230容器中腿堤,高度應(yīng)該怎么縮放
    // 因為我們后期要做圖片的延遲加載阀坏,在沒有圖片之前,我們也需要知道未來圖片要渲染的高度笆檀,這樣才能又一個容器先占位

   // 根據(jù)容器寬度(230)等比縮放數(shù)據(jù)中的圖片
   data.forEach(item => {
     let { width, height } = item;
     item.height = height / (width / 230);
     item.width = 230;
   })

    // 每三個為一組獲取數(shù)據(jù)
    for (let i = 0; i < data.length; i += 3) {
      // 將數(shù)組分為3列一組
      let group = data.slice(i, i + 3);

      // 獲取到當(dāng)前每一列的高度并進(jìn)行升序處理
      columns.sort((a, b) => b.offsetHeight - a.offsetHeight);

      // 把每一組的數(shù)據(jù)按照圖片高度進(jìn)行降序處理
      group.sort((a, b) => a.height - b.height);

      // 把告訴最小的圖片插入到高度最大的列中 (看不懂繼續(xù)看圖解)
      group.forEach((item, index) => {
        let { height, title, pic } = item;
        // 創(chuàng)建存放圖片的容器
        let card = document.createElement('div');
        card.className = "card";
        // 使用字符模板(es6)為容器添加內(nèi)容
        card.innerHTML = `<a href="#">
                    <div class="lazyImageBox" style="height:${height}px">
                        <img src="${pic}" alt="">
                    </div>
                    <p>${title}</p>
                </a>`;
        // 向每一列中添加數(shù)據(jù)
        columns[index].appendChild(card);
      });
    }
  }
  • 至此我們實現(xiàn)了瀑布流---接下來我們將實現(xiàn)圖片的懶加載
/*
 * 為啥要做圖片的延遲加載
 *   瀏覽器渲染頁面
 *      1.構(gòu)建DOM樹
 *      2.構(gòu)建CSSOM樹
 *      3.生成RENDER TREE (渲染樹)
 *      4.布局
 *      5.分層
 *      6.珊格化
 *      7.繪制
 * 構(gòu)建DOM樹中如果遇到img
 *   老版本瀏覽器:阻礙DOM渲染
 *   新版本瀏覽器:不會阻礙  每一個圖片請求都會占用一個HTTP(瀏覽器同時發(fā)送的HTTP 6個)
 *          拿回來資源后會和RENDER TREE一起渲染
 *   .....
 *   開始加載圖片忌堂,一定會讓頁面第一次渲染速度變慢(白屏)
 *
 * 圖片延遲加載:第一次不請求也不渲染圖片,等頁面加載完酗洒,其他資源都渲染好了士修,再去請求加載圖片
 */
懶加載的優(yōu)點
  1. 增強(qiáng)用戶體驗
  2. 優(yōu)化代碼
  3. 減少http的請求
  4. 減少服務(wù)器端壓力
  5. 服務(wù)器的按需加載
懶加載的原理

先將img標(biāo)簽中的src鏈接設(shè)為同一張圖片(空白圖片),將其真正的圖片地址存儲再img標(biāo)簽的自定義屬性中(比如data-src)樱衷。當(dāng)js監(jiān)聽到該圖片元素進(jìn)入可視窗口時棋嘲,即將自定義屬性中的地址存儲到src屬性中,達(dá)到懶加載的效果矩桂。
這樣做能防止頁面一次性向服務(wù)器響應(yīng)大量請求導(dǎo)致服務(wù)器響應(yīng)慢沸移,頁面卡頓或崩潰等問題。

  • 接下來我將使用 getBoundingClientRect方法實現(xiàn)圖片大的懶加載
    getBoundingClientRect 方法兼容性較好 但不是唯一的解決方案 在文章最后我會簡單介紹幾種其他的幾種方案
  1. 首先我們來了解下 getBoundingClientRect
  • 理解:getBoundingClientRect用于獲取某個元素相對于視窗的位置集合。集合中有top, right, bottom, left等屬性雹锣。

  • 返回值類型

  1. top:元素上邊到視窗頂部的距離;
  2. right:元素右邊到視窗頂部的距離;
  3. bottom:元素下邊到視窗頂部的距離;
  4. left:元素左邊到視窗頂部的距離;
  • 圖解


    getBoundingClientRect
  • 接下來我們使用代碼實現(xiàn)圖片的懶加載 注意看注釋

// 創(chuàng)建一個對象存放需要加載的圖片列表
let lazyImageBoxs;
// 獲取瀏覽器可視區(qū)域高度
let winH = document.documentElement.clientHeight;

function lazyFunc () {
  !lazyImageBoxs ? lazyImageBoxs = Array.from(document.querySelectorAll('.lazyImageBox')) : null;
   lazyImageBoxs.forEach(lazyImageBox => {
    // 已經(jīng)處理過則不在處理  詳情請看 lazyImg 函數(shù)
    let isLoad = lazyImageBox.getAttribute('isLoad');
    if (isLoad) return;
    // 若圖片底部距離瀏覽器頂部的距離小于瀏覽器的可視區(qū)域高度  那么說明此圖片已完全顯示出來
    let { bottom } = lazyImageBox.getBoundingClientRect();
    bottom <= winH && lazyImg(lazyImageBox);
  });
}

/**
 * 懶加載圖片
 * @param {dom} lazyImageBox 
 */
function lazyImg (lazyImageBox) {
  // img:獲取到當(dāng)前元素中的圖片网沾,trueImg:需要渲染的圖片路徑
  let img = lazyImageBox.querySelector('img'),
    trueImg = img.getAttribute('data-image');
  // 加載圖片
  img.src = trueImg;
  // 移除自定義屬性
  img.removeAttribute('data-image');
  // 添加isLoad屬性:表示當(dāng)前圖片已經(jīng)處理過了
  lazyImageBox.setAttribute('isLoad', 'true');
}

// 當(dāng)頁面渲染完成或滾動時都執(zhí)行圖片懶加載函數(shù)
window.onload = lazyFunc;
window.onscroll = lazyFunc;
  • 至此我們就實現(xiàn)了圖片的懶加載
    問題:注意看以下兩行代碼
window.onload = lazyFunc;
window.onscroll = lazyFunc;

這樣的弊端是什么

onscroll觸發(fā)的頻率太高了,滾動一下可能要被觸發(fā)很多次蕊爵,導(dǎo)致很多沒必要的計算和處理辉哥,消耗性能

  • 演示
    我們在以下函數(shù)中 添加console.log('OK'); 看頁面滾動的時候會觸發(fā)多少次
function lazyImg (lazyImageBox) {
  console.log('OK');
  //......
}

接下來我們看控制臺的輸出


這是我滑動滾輪5次函數(shù)執(zhí)行的次數(shù),看到這個數(shù)字你是否為頁面的性能感到焦慮在辆,那么應(yīng)該如何處理這種問題呢证薇?
這就用到了 函數(shù)節(jié)流
接下來我們在utils 工具類中寫一個節(jié)流方法

class utils {
  constructor() {
    console.log('工具類');
  }
  /*
     * throttle:實現(xiàn)函數(shù)的節(jié)流(目的是頻繁觸發(fā)中縮減頻率)
     *   @params
     *      func:需要執(zhí)行的函數(shù)
     *      wait:自己設(shè)定的間隔時間(頻率)
     *   @return
     *      可被調(diào)用執(zhí)行的函數(shù)
     */
  throttle (func, wait = 500) {
    let timer = null,
      previous = 0; //記錄上一次操作時間
    return function anonymous (...params) {
      let now = new Date(), //當(dāng)前操作的時間
        remaining = wait - (now - previous);
      if (remaining <= 0) {
        // 兩次間隔時間超過頻率:把方法執(zhí)行即可
        clearTimeout(timer);
        timer = null;
        previous = now;
        func.call(this, ...params);
      } else if (!timer) {
        // 兩次間隔時間沒有超過頻率,說明還沒有達(dá)到觸發(fā)標(biāo)準(zhǔn)呢匆篓,設(shè)置定時器等待即可(還差多久等多久)
        timer = setTimeout(() => {
          clearTimeout(timer);
          timer = null;
          previous = new Date();
          func.call(this, ...params);
        }, remaining);
      }
    };
  }
  /**
   * 封裝Ajax
   * @param {*} url 
   * @returns {Promise}
   */
  ajax (url) {
    return new Promise(resolve => {
      let xhr = new XMLHttpRequest;
      xhr.open('get', url);
      xhr.onreadystatechange = function () {
        (xhr.readyState === 4 && xhr.status === 200) && resolve(JSON.parse(xhr.responseText));
      };
      xhr.send();
    });
  }
}
  • 最后來so easy啦
window.onload = lazyFunc;
window.onscroll = utils.throttle(lazyFunc, 500);
  • 讓我們看下節(jié)流后的效果


    節(jié)流后效果

    我們可以看到同樣是滾動5次 函數(shù)的執(zhí)行次數(shù)由149 次變?yōu)?1次

其他懶加載方式:

  1. new IntersectionObserver
  2. 直接為img 添加loading="lazy"屬性:目前只有Chrome64 版本已上才兼容 感覺這將是未來的趨勢

具體實現(xiàn)方法我就不一一寫出來了浑度,大家有興趣的可以了解下,這兩種方法比現(xiàn)在用的這種方法要好美中不足的是兼容性還不行

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸦概,一起剝皮案震驚了整個濱河市箩张,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌窗市,老刑警劉巖先慷,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異咨察,居然都是意外死亡论熙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門摄狱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脓诡,“玉大人,你說我怎么就攤上這事媒役∽Q瑁” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵酣衷,是天一觀的道長交惯。 經(jīng)常有香客問我,道長穿仪,這世上最難降的妖魔是什么席爽? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮啊片,結(jié)果婚禮上只锻,老公的妹妹穿的比我還像新娘。我一直安慰自己钠龙,他們只是感情好炬藤,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布御铃。 她就那樣靜靜地躺著,像睡著了一般沈矿。 火紅的嫁衣襯著肌膚如雪上真。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天羹膳,我揣著相機(jī)與錄音睡互,去河邊找鬼。 笑死陵像,一個胖子當(dāng)著我的面吹牛就珠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播醒颖,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼妻怎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泞歉?” 一聲冷哼從身側(cè)響起逼侦,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腰耙,沒想到半個月后榛丢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡挺庞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年晰赞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片选侨。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡掖鱼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出侵俗,到底是詐尸還是另有隱情锨用,我是刑警寧澤丰刊,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布隘谣,位于F島的核電站,受9級特大地震影響啄巧,放射性物質(zhì)發(fā)生泄漏寻歧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一秩仆、第九天 我趴在偏房一處隱蔽的房頂上張望码泛。 院中可真熱鬧,春花似錦澄耍、人聲如沸噪珊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痢站。三九已至磷箕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阵难,已是汗流浹背岳枷。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留呜叫,地道東北人空繁。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像朱庆,于是被迫代替她去往敵國和親盛泡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348