原生js - 兩種圖片懶加載實現原理

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

  一、利用 getBoundingClientRect API獲得當前元素與視窗的距離來判斷數組

  二、利用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 方法來配合,對於瀏覽器的性能會有必定的問題。this

IntersectionObserver

  而 IntersectionObserver 方法是在2016年初提出來的,該API提供了一種異步觀察目標元素相對與 root 元素是否進入了可視區域。做爲一個新興API,會有必定的兼容問題, 點擊查看兼容性
  當 IntersectionObserver 對象被建立,其被配置爲監聽根中一段給定比例的可見區域。一旦 IntersectionObserver 被建立,則沒法更改其配置,因此一個給定的觀察者對象只能用來監聽可見區域的特定變化值,可是能夠在同一個觀察者對象中配置監聽多個目標元素。
 

 屬性

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

  rootMargin: 計算交叉時添加到根(root)邊界盒bounding box的矩形偏移量, 能夠有效的縮小或擴大根的斷定範圍從而知足計算須要。spa

  thresholds: 能夠是一個單獨的number,也能夠是一個number數組。當 root 元素與 target 元素相交達到該值的時候會執行回調。當值設定爲0時,那麼 target 元素有一個像素出如今 root 元素,回調就會執行;若是值設爲1,那麼就是當 target 元素徹底出如今 root 元素當中纔會執行。默認爲0。若是當值設定爲 [0, 0.25, 0.5, 0.75, 1] 時,那麼每知足一個值就會回調一次。該值設定的時候不是複數,當取值的時候爲複數:prototype

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

observer.thresholds // 複數

 方法

  IntersectionObserver.disconnect: 中止全部監聽工做code

  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();
  }
}
相關文章
相關標籤/搜索