原生JS實現最簡單的圖片懶載入

axuebin發表於2017-09-27

試一下自己擼一個圖片懶載入...

Demo地址:axuebin.com/lazyload
原始碼地址:github.com/axuebin/laz…

照片都是自己拍的哦~

懶載入

什麼是懶載入

懶載入其實就是延遲載入,是一種對網頁效能優化的方式,比如當訪問一個頁面的時候,優先顯示可視區域的圖片而不一次性載入所有圖片,當需要顯示的時候再傳送圖片請求,避免開啟網頁時載入過多資源。

什麼時候用懶載入

當頁面中需要一次性載入很多圖片的時候,往往都是需要用懶載入的。

懶載入原理

我們都知道HTML中的<img>標籤是代表文件中的一個影象。。說了個廢話。。

<img>標籤有一個屬性是src,用來表示影象的URL,當這個屬性的值不為空時,瀏覽器就會根據這個值傳送請求。如果沒有src屬性,就不會傳送請求。

嗯?貌似這點可以利用一下?

我先不設定src,需要的時候再設定?

nice,就是這樣。

我們先不給<img>設定src,把圖片真正的URL放在另一個屬性data-src中,在需要的時候也就是圖片進入可視區域的之前,將URL取出放到src中。

實現

HTML結構

<div class="container">
  <div class="img-area">
    <img class="my-photo" alt="loading" data-src="./img/img1.png">
  </div>
  <div class="img-area">
    <img class="my-photo" alt="loading" data-src="./img/img2.png">
  </div>
  <div class="img-area">
    <img class="my-photo" alt="loading" data-src="./img/img3.png">
  </div>
  <div class="img-area">
    <img class="my-photo" alt="loading" data-src="./img/img4.png">
  </div>
  <div class="img-area">
    <img class="my-photo" alt="loading" data-src="./img/img5.png">
  </div>
</div>複製程式碼

仔細觀察一下,<img>標籤此時是沒有src屬性的,只有altdata-src屬性。

alt 屬性是一個必需的屬性,它規定在影象無法顯示時的替代文字。
data-* 全域性屬性:構成一類名稱為自定義資料屬性的屬性,可以通過HTMLElement.dataset來訪問。

如何判斷元素是否在可視區域

方法一

網上看到好多這種方法,稍微記錄一下。

  1. 通過document.documentElement.clientHeight獲取螢幕可視視窗高度
  2. 通過element.offsetTop獲取元素相對於文件頂部的距離
  3. 通過document.documentElement.scrollTop獲取瀏覽器視窗頂部與文件頂部之間的距離,也就是滾動條滾動的距離

然後判斷②-③<①是否成立,如果成立,元素就在可視區域內。

方法二 getBoundingClientRect

通過getBoundingClientRect()方法來獲取元素的大小以及位置,MDN上是這樣描述的:

The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.

這個方法返回一個名為ClientRectDOMRect物件,包含了toprightbottonleftwidthheight這些值。

MDN上有這樣一張圖:

可以看出返回的元素位置是相對於左上角而言的,而不是邊距。

我們思考一下,什麼情況下圖片進入可視區域。

假設const bound = el.getBoundingClientRect();來表示圖片到可視區域頂部距離;
並設 const clientHeight = window.innerHeight;來表示可視區域的高度。

隨著滾動條的向下滾動,bound.top會越來越小,也就是圖片到可視區域頂部的距離越來越小,當bound.top===clientHeight時,圖片的上沿應該是位於可視區域下沿的位置的臨界點,再滾動一點點,圖片就會進入可視區域。

也就是說,在bound.top<=clientHeight時,圖片是在可視區域內的。

我們這樣判斷:

function isInSight(el) {
  const bound = el.getBoundingClientRect();
  const clientHeight = window.innerHeight;
  //如果只考慮向下滾動載入
  //const clientWidth = window.innerWeight;
  return bound.top <= clientHeight + 100;
}複製程式碼

這裡有個+100是為了提前載入。

載入圖片

頁面開啟時需要對所有圖片進行檢查,是否在可視區域內,如果是就載入。

function checkImgs() {
  const imgs = document.querySelectorAll('.my-photo');
  Array.from(imgs).forEach(el => {
    if (isInSight(el)) {
      loadImg(el);
    }
  })
}

function loadImg(el) {
  if (!el.src) {
    const source = el.dataset.src;
    el.src = source;
  }
}複製程式碼

這裡應該是有一個優化的地方,設一個識別符號標識已經載入圖片的index,當滾動條滾動時就不需要遍歷所有的圖片,只需要遍歷未載入的圖片即可。

函式節流

在類似於滾動條滾動等頻繁的DOM操作時,總會提到“函式節流、函式去抖”。

所謂的函式節流,也就是讓一個函式不要執行的太頻繁,減少一些過快的呼叫來節流。

基本步驟:

  1. 獲取第一次觸發事件的時間戳
  2. 獲取第二次觸發事件的時間戳
  3. 時間差如果大於某個閾值就執行事件,然後重置第一個時間
function throttle(fn, mustRun = 500) {
  const timer = null;
  let previous = null;
  return function() {
    const now = new Date();
    const context = this;
    const args = arguments;
    if (!previous){
      previous = 0;
    }
    const remaining = now - previous;
    if (mustRun && remaining >= mustRun) {
      fn.apply(context, args);
      previous = now;
    }
  }
}複製程式碼

這裡的mustRun就是呼叫函式的時間間隔,無論多麼頻繁的呼叫fn,只有remaining>=mustRunfn才能被執行。

實驗

頁面開啟時

可以看出此時僅僅是載入了img1和img2,其它的img都沒傳送請求,看看此時的瀏覽器

第一張圖片是完整的呈現了,第二張圖片剛進入可視區域,後面的就看不到了~

頁面滾動時

當我向下滾動,此時瀏覽器是這樣

此時第二張圖片完全顯示了,而第三張圖片顯示了一點點,這時候我們看看請求情況

img3的請求發出來,而後面的請求還是沒發出~

全部載入時

當滾動條滾到最底下時,全部請求都應該是發出的,如圖

完整demo

在這哦:axuebin.com/lazyload

更新

方法三 IntersectionObserver

經大佬提醒,發現了這個方法

先附上鍊接:

jjc大大:github.com/justjavac/t…

阮一峰大大:www.ruanyifeng.com/blog/2016/1…

API Sketch for Intersection Observers:github.com/WICG/Inters…

IntersectionObserver可以自動觀察元素是否在視口內。

var io = new IntersectionObserver(callback, option);
// 開始觀察
io.observe(document.getElementById('example'));
// 停止觀察
io.unobserve(element);
// 關閉觀察器
io.disconnect();複製程式碼

callback的引數是一個陣列,每個陣列都是一個IntersectionObserverEntry物件,包括以下屬性:

屬性 描述
time 可見性發生變化的時間,單位為毫秒
rootBounds 與getBoundingClientRect()方法的返回值一樣
boundingClientRect 目標元素的矩形區域的資訊
intersectionRect 目標元素與視口(或根元素)的交叉區域的資訊
intersectionRatio 目標元素的可見比例,即intersectionRect佔boundingClientRect的比例,完全可見時為1,完全不可見時小於等於0
target 被觀察的目標元素,是一個 DOM 節點物件

我們需要用到intersectionRatio來判斷是否在可視區域內,當intersectionRatio > 0 && intersectionRatio <= 1即在可視區域內。

程式碼

function checkImgs() {
  const imgs = Array.from(document.querySelectorAll(".my-photo"));
  imgs.forEach(item => io.observe(item));
}

function loadImg(el) {
  if (!el.src) {
    const source = el.dataset.src;
    el.src = source;
  }
}

const io = new IntersectionObserver(ioes => {
  ioes.forEach(ioe => {
    const el = ioe.target;
    const intersectionRatio = ioe.intersectionRatio;
    if (intersectionRatio > 0 && intersectionRatio <= 1) {
      loadImg(el);
    }
    el.onload = el.onerror = () => io.unobserve(el);
  });
});複製程式碼

相關文章