目前圖片懶加載的方式主要有兩種:css
一、利用 getBoundingClientRect API獲得當前元素與視窗的距離來判斷數組
二、利用h5的新API IntersectionObserver 來實現瀏覽器
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 方法來配合,對於瀏覽器的性能會有必定的問題。this
root: 所監聽對象的具體祖先元素。若是未傳入值或值爲null
,則默認使用頂級文檔的視窗。url
rootMargin: 計算交叉時添加到根(root)邊界盒bounding box的矩形偏移量, 能夠有效的縮小或擴大根的斷定範圍從而知足計算須要。spa
thresholds: 能夠是一個單獨的number,也能夠是一個number數組。當 root 元素與 target 元素相交達到該值的時候會執行回調。當值設定爲0時,那麼 target 元素有一個像素出如今 root 元素,回調就會執行;若是值設爲1,那麼就是當 target 元素徹底出如今 root 元素當中纔會執行。默認爲0。若是當值設定爲 [0, 0.25, 0.5, 0.75, 1] 時,那麼每知足一個值就會回調一次。該值設定的時候不是複數,當取值的時候爲複數:prototype
var observer = new IntersectionObserver(_observer, { root : null, threshold: [] // 單數 }); observer.thresholds // 複數
IntersectionObserver.disconnect: 中止全部監聽工做code
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(); } }