目前圖片懶載入的方式主要有兩種:
1、利用 getBoundingClientRect API得到當前元素與視窗的距離來判斷
2、利用h5的新API IntersectionObserver 來實現
getBoundingClientRect
Element.getBoundingClientRect() 方法返回值是一個 DOMRect 物件,包含了該元素一組矩形的集合:是與該元素相關的css邊框集合(top, left, right, bottom)。
我們可以採用如下方法來判斷是否在可視區域:
isViewport (el) { const viewWidth = window.innerWidth || document.documentElement.clientWidth; const viewHeight = window.innerHeight || document.documentElement.clientHeight; let { top, left, right, bottom } = el.getBoundingClientRect() return ( top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeight ) }
getBoundingClientRect 方式來實現需要監聽 scroll 方法來配合,對於瀏覽器的效能會有一定的問題。
IntersectionObserver
屬性
root: 所監聽物件的具體祖先元素。如果未傳入值或值為null
,則預設使用頂級文件的視窗。
rootMargin: 計算交叉時新增到根(root)邊界盒bounding box的矩形偏移量, 可以有效的縮小或擴大根的判定範圍從而滿足計算需要。
thresholds: 可以是一個單獨的number,也可以是一個number陣列。當 root 元素與 target 元素相交達到該值的時候會執行回撥。當值設定為1時,那麼 target 元素有一個畫素出現在 root 元素,回撥就會執行;如果值設為1,那麼就是當 target 元素完全出現在 root 元素當中才會執行。預設為0。如果當值設定為 [0, 0.25, 0.5, 0.75, 1] 時,那麼每滿足一個值就會回撥一次。該值設定的時候不是複數,當取值的時候為複數:
var observer = new IntersectionObserver(_observer, { root : null, threshold: [] // 單數 }); observer.thresholds // 複數
方法
IntersectionObserver.disconnect: 停止所有監聽工作
IntersectionObserver.observe: 開始監聽一個目標元素
IntersectionObserver.takeRecords: 返回所有觀察目標物件的陣列
IntersectionObserver.unobserve: 停止監聽特定目標元素。
function LazyLoad (config) { this.default = { root: null, threshold: 0 } this.settings = Object.assign(this.default, config) this.images = [] this.observer = null this.init() } LazyLoad.prototype = { init () { if (!window.IntersectionObserver) { this.loadImages() return } this.images = document.querySelectorAll(this.settings.selector || '[data-src]') let _this = this let observeConfig = { root: this.settings.root, rootMargin: this.settings.rootMargin, threshold: [this.settings.threshold] } this.observer = new IntersectionObserver(changes => { Array.prototype.forEach.call(changes, entry => { if (entry.isIntersecting) { let target = entry.target _this.observer.unobserve(target) let src = target.dataset.src if (target.tagName.toLowerCase() === 'img') { target.src = src } else { target.style.backgroundImage = `url(${src})` } target.removeAttribute('data-src') } }) }, observeConfig) Array.prototype.forEach.call(this.images, image => { _this.observer.observe(image) image.src = _this.settings.placeholder }) }, loadImages () { let _this = this _this.replaceSrc() let hasDone = false function _scroll() { if (hasDone) return hasDone = true setTimeout(() => { _this.replaceSrc() hasDone = false }, 100) } window.onscroll = _scroll }, replaceSrc () { let _this = this let imgs = document.querySelectorAll(this.settings.selector || '[data-src]') Array.prototype.forEach.call(imgs, image => { if (!image.src) image.src = _this.settings.placeholder let src = image.dataset.src if (_this.isInnerView(image)) { if (image.tagName.toLowerCase() === 'img') { image.src = src } else { image.style.backgroundImage = `url(${src})` } image.removeAttribute('data-src') } }) }, isInnerView (el) { const viewWidth = window.innerWidth || document.documentElement.clientWidth; const viewHeight = window.innerHeight || document.documentElement.clientHeight; let { top, left, right, bottom } = el.getBoundingClientRect() return ( top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeight ) }, destroy () { this.observer.disconnect(); } }