原生js - 两种图片懒加载实现原理

目前图片懒加载的方式主要有两种:

  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 元素相交达到该值的时候会执行回调。当值设定为0时,那么 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();
  }
}

 

 
 
 
posted @ 2019-07-16 16:31  前端杂货  阅读(2091)  评论(1编辑  收藏  举报