在咱們實際業務開發過程當中,常常須要用到圖片懶加載,也就是滾動加載。其功能就是,若是圖片在可視區域,則觸發加載邏輯。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的圖片懶加載組件,哪裏想用就用哪裏,徹底不用操心具體細節。
參考: