在咱们实际业务开发过程当中,常常须要用到图片懒加载,也就是滚动加载。其功能就是,若是图片在可视区域,则触发加载逻辑。javascript
咱们传统的实现方式,则是经过监听scroll
事件,经过计算元素的(height、width、offset)等,判断该元素是否在可视区域,而后出发加载动做。java
经过这种实现方式有不少库或者demo代码,这就不重复多说了。浏览器
可是这种方式的弊端也不少,因为滚动过程当中,会触发大量scroll
事件,所以咱们通常还会结合debounce
,减小每次触发逻辑,提示性能。(每次获取元素的height、width、style等都会触发reflow
,稍微不甚就会引发严重的性能问题)微信
在现代浏览器经过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 轻松打造<img-lazy></img-lazy>
组件,在须要用到地方直接替换原有<img/>
,则自动有懒加载和点击重试的功能,方便、好用。
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的图片懒加载组件,哪里想用就用哪里,彻底不用操心具体细节。
参考: