IntersectionObserver + Custom Elements 實現圖片懶加載(滾動加載)/點擊重試

前言

在咱們實際業務開發過程當中,常常須要用到圖片懶加載,也就是滾動加載。其功能就是,若是圖片在可視區域,則觸發加載邏輯。javascript

傳統實現方式

咱們傳統的實現方式,則是經過監聽scroll事件,經過計算元素的(height、width、offset)等,判斷該元素是否在可視區域,而後出發加載動做。java

經過這種實現方式有不少庫或者demo代碼,這就不重複多說了。瀏覽器

可是這種方式的弊端也不少,因爲滾動過程當中,會觸發大量scroll事件,所以咱們通常還會結合debounce,減小每次觸發邏輯,提示性能。(每次獲取元素的height、width、style等都會觸發reflow,稍微不甚就會引發嚴重的性能問題)微信

現代瀏覽器實現方式

在現代瀏覽器經過IntersectionObserver則能夠比較簡單、優雅的實現。網絡

IntersectionObserver

IntersectionObserver接口 (從屬於Intersection Observer API) 提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗(viewport)交叉狀態的方法。異步

can be used to understand the visibility and position of DOM elements relative to a containing element or to the top-level viewport. The position is delivered asynchronously and is useful for understanding the visibility of elements and implementing pre-loading and deferred loading of DOM content.async

目前在Chrome支持比較不錯,若是不須要考慮兼容性問題,則放開手腳去用。性能

經過IntersectionObserver把全部圖片對象進行監聽,當圖像出如今可視區域,IntersectionObserver則會把可視區域的對象進行回調,這時候進行加載便可完成。今後不須要操心何時在可視區域,要怎麼計算,要怎麼提高性能。ui

TIPS:一旦圖片加載完成,記得unobserve移除監聽事件,提高性能。this

IntersectionObserver懶加載圖片的示例代碼,網絡上也不少,這就不重複寫了。

但這不是咱們的終極目標,咱們目標是打造相似網易新聞、微信公衆號那樣,圖片懶加載,並且加載失敗還能點擊重試。而且使用時候不須要硬編碼,各類鉤子才能使用。

IntersectionObserver + Custom Elements

經過使用IntersectionObserver + Custom Elements 輕鬆打造<img-lazy></img-lazy>組件,在須要用到地方直接替換原有<img/>,則自動有懶加載和點擊重試的功能,方便、好用。

Custom Elements

Method of defining new HTML tags.

簡單來講,這是一個實現自定義元素的API(相似Vue的Compont),可是這是瀏覽器標準。 兼容性以下:

注意,還有個舊的API:document.registerElement()

registerElement已經從 Web 標準中刪除,雖然一些瀏覽器目前仍然支持它,但也許會在將來的某個時間中止支持,請儘可能不要使用該特性。

不建議使用document.registerElement(),請使用customElements.define()

可是本文仍是基於document.registerElement()實現,廢話不說,上代碼。

var io = new IntersectionObserver((entries) => {
  entries.forEach((e) => {
    if (e.intersectionRatio < 0.25) return
    let el = e.target
    // 加載圖片
    loadImage(el, el.getAttribute('src'))
    el = null
  })
}, {
  threshold: [0.5]
})


var loadImage = (root, url) => {
  if (!url) return
  let el = root.shadowRoot.querySelector('img')
  // 把圖片地址暫存,失敗重試時候使用
  el._url = url
  var img = new Image()
  img.onload = () => {
    // 加載成功,清除引用,方便垃圾回收
    el._url = null
    el.onclick = null
    el.src = url
    img = null
    el = null
  }
  img.onerror = () => {
    // 若是加載失敗,點擊自動重試
    el.onclick = function () {
      loadImage(root, this._url)
    }
    img = null
    el = null
  }
  img.src = url
  // 清除監聽,失敗了須要手動點擊重試,不會再觸發自動加載
  io.unobserve(root)
}


const prototype = Object.create(HTMLElement.prototype, {
  createdCallback: {
    value () {
      // 建立自定義元素,而且經過shadowRoot,展現真正的img
      let root = this.createShadowRoot()
      root.innerHTML = '<img src="/empty.png">'
    }
  },
  attachedCallback: {
    value () {
      if (this.getAttribute('src')) {
        io.observe(this)
      }
    }
  },
  // 移除的時候,自動取消監聽
  detachedCallback: {
    value () {
      io.unobserve(this)
    }
  },
  // 當屬性變動時候,從新監聽
  attributeChangedCallback: {
    value (attrName, oldVal, newVal) {
      if (attrName == 'src') {
        io.observe(this)
      }
    }
  }
})


document.registerElement('img-lazy', {
  prototype: prototype
})
複製代碼

最後

經過以上方式,打造了一個基於WebComponent的圖片懶加載組件,哪裏想用就用哪裏,徹底不用操心具體細節。

參考:

相關文章
相關標籤/搜索