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

懶加載

------------------------------------------------------------------------------------------------html

什麼是懶加載

懶加載其實就是延遲加載,是一種對網頁性能優化的方式,好比當訪問一個頁面的時候,優先顯示可視區域的圖片而不一次性加載全部圖片,當須要顯示的時候再發送圖片請求,避免打開網頁時加載過多資源。

何時用懶加載

當頁面中須要一次性載入不少圖片的時候,每每都是須要用懶加載的。
git

懶加載原理

咱們都知道HTML中的 <img>標籤是表明文檔中的一個圖像。。說了個廢話。。github

<img>標籤有一個屬性是 src,用來表示圖像的URL,當這個屬性的值不爲空時,瀏覽器就會根據這個值發送請求。若是沒有 src屬性,就不會發送請求。api

嗯?貌似這點能夠利用一下?數組

我先不設置 src,須要的時候再設置?瀏覽器

nice,就是這樣。性能優化

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

實現

------------------------------------------------------------------------------------------------app

HTML結構

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

alt 屬性是一個必需的屬性,它規定在圖像沒法顯示時的替代文本。 data-* 全局屬性:構成一類名稱爲自定義數據屬性的屬性,能夠經過 HTMLElement.dataset來訪問。

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

方法一

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

  1. 經過 document.documentElement.clientHeight獲取屏幕可視窗口高度

  2. 經過 document.documentElement.scrollTop獲取瀏覽器窗口頂部與文檔頂部之間的距離,也就是滾動條滾動的距離

  3. 經過 element.offsetTop獲取元素相對於文檔頂部的距離

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

方法二(推薦)

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

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

這個方法返回一個名爲 ClientRectDOMRect對象,包含了 toprightbottonleftwidthheight這些值。

MDN上有這樣一張圖:

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

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

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

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

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

咱們這樣判斷:

這裏有個+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 = now;
    }
    const remaining = now - previous;
    if (mustRun && remaining >= mustRun) {
      fn.apply(context, args);
      previous = now;
    }
  }
}
複製代碼

這裏的 mustRun就是調用函數的時間間隔,不管多麼頻繁的調用 fn,只有 remaining>=mustRunfn才能被執行。

實驗

------------------------------------------------------------------------------------------------

頁面打開時



能夠看出此時僅僅是加載了img1和img2,其它的img都沒發送請求,看看此時的瀏覽器


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

頁面滾動時

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

此時第二張圖片徹底顯示了,而第三張圖片顯示了一點點,這時候咱們看看請求狀況

img3的請求發出來,然後面的請求仍是沒發出~

所有載入時

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

更新

------------------------------------------------------------------------------------------------

方法三 IntersectionObserver


阮一峯大大:

http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html

API Sketch for Intersection Observers:

https://github.com/WICG/IntersectionObserver

IntersectionObserver能夠自動觀察元素是否在視口內

  1. var io = new IntersectionObserver(callback, option);


  2. // 開始觀察


  3. io.observe(document.getElementById('example'));


  4. // 中止觀察


  5. io.unobserve(element);


  6. // 關閉觀察器


  7. 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);  });});複製代碼

文章來源於公衆號裏,如有侵權請告之!

相關文章
相關標籤/搜索