IntersectionObserver 和懶加載

IntersectionObserver 這個 API 日常可能聽得比較少,caniuse 兼容性報告目前支持率是 90.12%,還不推薦用於大衆化的場景中,可是它的能力和性能很是的好。html

IntersectionObserver

關於 IntersectionObserver

示意圖

IntersectionObserver 接口 (從屬於Intersection Observer API) 提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗(viewport)交叉狀態的方法。祖先元素與視窗(viewport)被稱爲根(root)。因爲可見(visible)的本質是,目標元素與視口產生一個交叉區,因此這個 API 叫作"交叉觀察器"。前端

簡單點說就是它能夠觀察 root(默認是視口)和目標元素的交叉狀況,當交叉率是 0% 或者 10% 或者更多的時候,能夠觸發指定的回調。api

當一個 IntersectionObserver 對象被建立時,其被配置爲監聽根中一段給定比例的可見區域。一旦 IntersectionObserver 被建立,則沒法更改其配置,因此一個給定的觀察者對象只能用來監聽可見區域的特定變化值;然而,你能夠在同一個觀察者對象中配置監聽多個目標元素。數組

使用

const observer = new IntersectionObserver(callback, observerConfig)瀏覽器

建立一個新的 IntersectionObserver 對象,當其監聽到目標元素的可見部分穿過了一個或多個閾(thresholds)時,會執行指定的回調函數。性能優化

用法異步

function cb (entries) {
  console.log(entries)
  entries.forEach(entry => {
    const target = entry.target;
    console.log(target)
    console.log(entry)
  });
}

let observerConfig = {
  root: null,
  rootMargin: '0px',
  threshold: [0],
}

const observer = new IntersectionObserver(cb, observerConfig)
const box = document.getElementById('#box')
// 開始觀察
observer.observe(box)
// 中止觀察
observer.unobserve(box)
// 關閉觀察器,observer 全部的觀察都會中止
observer.disconnect();
複製代碼

entries 是一個監聽目標的數組,每一個成員都是一個 IntersectionObserverEntry 對象。函數

cb 回調函數在最初會調用一次,此次 entries 會是全部的觀察目標對象,在滑動的時候會把可見性變化符合 threshold 的對象做爲 entries 傳進來。性能

初始化(所有被觀察的) 優化

image

滾動時(符合條件的)

image

因爲是異步的操做,能夠看到 start end 這兩個同步操做執行以後,纔會執行 cb

IntersectionObserverEntry

{
  boundingClientRect: DOMRectReadOnly {x: 8, y: 380, width: 300, height: 300, top: 380, …}
  intersectionRatio: 0.023333333432674408
  intersectionRect: DOMRectReadOnly {x: 8, y: 380, width: 300, height: 7, top: 380, …}
  isIntersecting: true
  rootBounds: DOMRectReadOnly {x: 0, y: 0, width: 1903, height: 387, top: 0, …}
  target: img.lazy
  time: 440149.8099999735
}
複製代碼

含義以下

  • time:可見性發生變化的時間,是一個高精度時間戳,單位爲毫秒,返回一個記錄從 IntersectionObserver 的時間原點(time origin)到交叉被觸發的時間的時間戳(DOMHighResTimeStamp).
  • target:被觀察的目標元素,是一個 DOM 節點對象
  • rootBounds:根元素的矩形區域的信息,getBoundingClientRect() 方法的返回值,若是沒有根元素(即直接相對於視口滾動),則返回 null
  • boundingClientRect:目標元素的矩形區域的信息
  • intersectionRect:目標元素與視口(或根元素)的交叉區域的信息
  • intersectionRatio:目標元素的可見比例,即 intersectionRectboundingClientRect 的比例,徹底可見時爲 1,徹底不可見時小於等於0
  • isIntersecting 是否交叉

配置

observerConfig.root 所監聽對象的具體祖先元素(element)。若是未傳入值或值爲 null,則默認使用頂級文檔的視窗。

observerConfig.rootMargin 計算交叉時添加到根(root)邊界盒 bounding box 的矩形偏移量, 能夠有效的縮小或擴大根的斷定範圍從而知足計算須要。全部的偏移量都可用像素(pixel)(px)或百分比(percentage)(%)來表達, 默認值爲"0px 0px 0px 0px",用法和普通的 margin 同樣(top、right、bottom、left)。

observerConfig.threshold 注意 MDN 文檔中的 thresholds 是錯誤的,一個包含閾值的列表, 按升序排列, 列表中的每一個閾值都是監聽對象的交叉區域與邊界區域的比率。當監聽對象的任何閾值被越過期,都會生成一個通知(Notification)。若是構造器未傳入值, 則默認值爲0,也能夠是 [0, 0.25, 0.5, 0.75, 1]

懶加載的常規實現

常規實現都會監聽滾動事件,經過 el.getBoundingClientRect() 獲取到當前元素與視口的位置關係來肯定圖片是否加載,在加載完成以後爲了性能考慮,刪除 data-src ,這樣就能夠避免重複的執行,要注意的是 getBoundingClientRect 會觸發瀏覽器的迴流

getBoundingClientRect 這個方法的支持度已經很是高,能夠放心使用。

getBoundingClientRect

核心是下面這個判斷,這個方法能夠只加載和視口交叉的元素,不會加載視口上面或者視口下面的圖片。

$img.dataset.src
  && $img.getBoundingClientRect().bottom >= 0
  && windowHeight > $img.getBoundingClientRect().top
複製代碼
  1. 若是不存在 data-src 則直接跳過(性能優化);
  2. 判斷元素底部是否出如今視口中,出現則顯示;
  3. 判斷元素頂部是否出如今視口中,出現則顯示;
document.addEventListener('DOMContentLoaded', function() {
  const imgs = document.querySelectorAll('.lazy')

  function lazyLoad() {
    const windowHeight = document.documentElement.clientHeight
    imgs.forEach(($img, i) => {
      // 重點是下面這個判斷
      if ($img.dataset.src && $img.getBoundingClientRect().bottom >= 0 && windowHeight > $img.getBoundingClientRect().top) {
        $img.src = $img.dataset.src
        delete $img.dataset.src
      }
    })
  }

  lazyLoad()
  document.addEventListener('scroll', debounce(lazyLoad, 200))
})

function debounce(func, wait) {
  let timer = null

  return function(...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func(...args)
    }, wait)
  }
}
複製代碼

感謝閱讀,歡迎關注個人公衆號 雲影sky,帶你解讀前端技術。關注公衆號能夠拉你進討論羣,有任何問題都會回覆。

公衆號
相關文章
相關標籤/搜索