IntersectionObserver + Custom Elements 实现图片懒加载(滚动加载)/点击重试

前言

在我们实际业务开发过程中,经常需要用到图片懒加载,也就是滚动加载。其功能就是,如果图片在可视区域,则触发加载逻辑。

传统实现方式

我们传统的实现方式,则是通过监听scroll事件,通过计算元素的(height、width、offset)等,判断该元素是否在可视区域,然后出发加载动作。

通过这种实现方式有很多库或者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.

目前在Chrome支持比较不错,如果不需要考虑兼容性问题,则放开手脚去用。

通过IntersectionObserver把所有图片对象进行监听,当图像出现在可视区域,IntersectionObserver则会把可视区域的对象进行回调,这时候进行加载即可完成。从此不需要操心什么时候在可视区域,要怎么计算,要怎么提升性能。

TIPS:一旦图片加载完成,记得unobserve移除监听事件,提升性能。

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

参考:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值