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的图片懒加载组件,哪里想用就用哪里,彻底不用操心具体细节。

参考:

相关文章
相关标签/搜索