前言
在我们实际业务开发过程中,经常需要用到图片懒加载,也就是滚动加载。其功能就是,如果图片在可视区域,则触发加载逻辑。
传统实现方式
我们传统的实现方式,则是通过监听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:
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 = '' } }, 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的图片懒加载组件,哪里想用就用哪里,完全不用操心具体细节。
参考: