圖片懶載入實現

前端雜貨發表於2019-07-16

目前圖片懶載入的方式主要有兩種:

  1、利用 getBoundingClientRect API得到當前元素與視窗的距離來判斷

  2、利用h5的新API IntersectionObserver 來實現

getBoundingClientRect

  Element.getBoundingClientRect() 方法返回值是一個 DOMRect 物件,包含了該元素一組矩形的集合:是與該元素相關的css邊框集合(top, left, right, bottom)。 

我們可以採用如下方法來判斷是否在可視區域:

isViewport (el) {
  const viewWidth = window.innerWidth || document.documentElement.clientWidth;
  const viewHeight = window.innerHeight || document.documentElement.clientHeight;
  let { top, left, right, bottom } = el.getBoundingClientRect()
  return (
    top >= 0 &&
    left >= 0 &&
    right <= viewWidth &&
    bottom <= viewHeight
  )
}

getBoundingClientRect 方式來實現需要監聽 scroll 方法來配合,對於瀏覽器的效能會有一定的問題。

IntersectionObserver

  而 IntersectionObserver 方法是在2016年初提出來的,該API提供了一種非同步觀察目標元素相對與 root 元素是否進入了可視區域。作為一個新興API,會有一定的相容問題,點選檢視相容性
  當 IntersectionObserver 物件被建立,其被配置為監聽根中一段給定比例的可見區域。一旦 IntersectionObserver 被建立,則無法更改其配置,所以一個給定的觀察者物件只能用來監聽可見區域的特定變化值,但是可以在同一個觀察者物件中配置監聽多個目標元素。
 

 屬性

  root: 所監聽物件的具體祖先元素。如果未傳入值或值為null,則預設使用頂級文件的視窗。

  rootMargin: 計算交叉時新增到根(root)邊界盒bounding box的矩形偏移量, 可以有效的縮小或擴大根的判定範圍從而滿足計算需要。

  thresholds: 可以是一個單獨的number,也可以是一個number陣列。當 root 元素與 target 元素相交達到該值的時候會執行回撥。當值設定為1時,那麼 target 元素有一個畫素出現在 root 元素,回撥就會執行;如果值設為1,那麼就是當 target 元素完全出現在 root 元素當中才會執行。預設為0。如果當值設定為 [0, 0.25, 0.5, 0.75, 1] 時,那麼每滿足一個值就會回撥一次。該值設定的時候不是複數,當取值的時候為複數:

var observer = new IntersectionObserver(_observer, {
    root : null,
    threshold: [] // 單數
});

observer.thresholds // 複數

 方法

  IntersectionObserver.disconnect: 停止所有監聽工作

  IntersectionObserver.observe: 開始監聽一個目標元素

  IntersectionObserver.takeRecords: 返回所有觀察目標物件的陣列

  IntersectionObserver.unobserve: 停止監聽特定目標元素。

 
下面添上圖片懶載入 js 程式碼:
function LazyLoad (config) {
  this.default = {
    root: null,
    threshold: 0
  }
  this.settings = Object.assign(this.default, config)
  this.images = []
  this.observer = null
  this.init()
}

LazyLoad.prototype = {
  init () {
    if (!window.IntersectionObserver) {
      this.loadImages()
      return
    }

    this.images = document.querySelectorAll(this.settings.selector || '[data-src]')

    let _this = this
    let observeConfig = {
      root: this.settings.root,
      rootMargin: this.settings.rootMargin,
      threshold: [this.settings.threshold]
    }

    this.observer = new IntersectionObserver(changes => {
      Array.prototype.forEach.call(changes, entry => {

        if (entry.isIntersecting) {

          let target = entry.target
          _this.observer.unobserve(target)
          let src = target.dataset.src

          if (target.tagName.toLowerCase() === 'img') {
            target.src = src
          } else {
            target.style.backgroundImage = `url(${src})`
          }

          target.removeAttribute('data-src')
        }
      })
    }, observeConfig)

    Array.prototype.forEach.call(this.images, image => {
      _this.observer.observe(image)
      image.src = _this.settings.placeholder
    })

  },

  loadImages () {
    let _this = this
    _this.replaceSrc()

    let hasDone = false
    function _scroll() {
      if (hasDone) return
      hasDone = true
      setTimeout(() => {
        _this.replaceSrc()
        hasDone = false
      }, 100)
    }
    window.onscroll = _scroll
  },

  replaceSrc () {
    let _this = this
    let imgs = document.querySelectorAll(this.settings.selector || '[data-src]')
    Array.prototype.forEach.call(imgs, image => {
      if (!image.src) image.src = _this.settings.placeholder
      let src = image.dataset.src
      if (_this.isInnerView(image)) {
        if (image.tagName.toLowerCase() === 'img') {
          image.src = src
        } else {
          image.style.backgroundImage = `url(${src})`
        }
        image.removeAttribute('data-src')
      }
    })
  },

  isInnerView (el) {
    const viewWidth = window.innerWidth || document.documentElement.clientWidth;
    const viewHeight = window.innerHeight || document.documentElement.clientHeight;
    let { top, left, right, bottom } = el.getBoundingClientRect()
    return (
      top >= 0 &&
      left >= 0 &&
      right <= viewWidth &&
      bottom <= viewHeight
    )
  },

  destroy () {
    this.observer.disconnect();
  }
}

 

 
 
 

相關文章