摘要:配置項配置項中的參數有以下三個所監聽對象的具體祖先元素,默認是計算交叉狀態時,將附加到祖先元素上,從而有效的擴大或者縮小祖先元素判定區域設置一系列的閾值,當交叉狀態達到閾值時,會觸發回調函數。
一、前言
??通常情況下,HTML 中的圖片資源會自上而下依次加載,而部分圖片只有在用戶向下滾動頁面的場景下才能被看見,否則這部分圖片的流量就白白浪費了。
??所以,對于那些含有大量圖片資源的網站,會采用“按需加載”的方式,也就是當圖片資源出現在視口區域內,才會被加載,這樣可能會影響一丟丟用戶體驗,但是能大大節省網站的流量。
??而上述“按需加載”的方式就是今天的主角 -- 圖片懶加載技術
二、原理??圖片懶加載技術主要通過監聽圖片資源容器是否出現在視口區域內,來決定圖片資源是否被加載。
??那么實現圖片懶加載技術的核心就是如何判斷元素處于視口區域之內。
三、前世??早前實現的思路:
給目標元素指定一張占位圖,將真實的圖片鏈接存儲在自定義屬性中,通常是data-src;
監聽與用戶滾動行為相關的 scroll 事件;
在 scroll 事件處理程序中利用 Element.getBoundingClientRect() 方法判斷目標元素與視口的交叉狀態;
當目標元素與視口的交叉狀態大于0時,將真實的圖片鏈接賦給目標元素 src 屬性或者 backgroundImage 屬性。
??scroll 事件可能會被高頻度的觸發,而按照上述思路,必然會在 scroll 事件處理程序中出現大量的 DOM 操作,這很可能會使頁面不再“如絲般順滑”,這時就需要降低 DOM 操作的頻率。
??降低 DOM 操作的頻率可以采用函數節流的方式,函數節流能夠確保在固定時間間隔只執行一次操作。
??與函數節流相關的另一個概念叫做函數防抖,這位兄弟同樣等待一個時間間隔執行操作,但是它被打斷之后就需要重新開始計時。
??這也是此場景下采用函數節流的原因。在 JavaScript 中可以采用 setTimeout + 閉包的方式實現函數節流:
function throttle (fn, interval = 500) { let timer = null let firstTime = true return function (...args) { if (firstTime) { // 第一次加載 fn.apply(this, args) return firstTime = false } if (timer) { // 定時器正在執行中,跳過 return } timer = setTimeout(() => { clearTimeout(timer) timer = null fn.apply(this, args) }, interval) } }
??除了上述 setTimeout + 閉包的實現方式,還可以通過 window.requestAnimationFrame() 方法實現,這里不再贅述,有興趣的同學可以自己嘗試嘗試。
??JavaScript 提供 Element.getBoundingClientRect() 方法返回元素的大小以及相對于視口的位置信息,接下來會用到返回對象的四個屬性:
top 和 left 是目標元素左上角坐標與網頁左上角坐標的偏移值;
width 和 height 是目標元素自身的寬度和高度。
??再結合視口的高度和寬度,即可判斷元素是否出現在視口區域內:
function isElementInViewport (el) { const { top, height, left, width } = el.getBoundingClientRect() const w = window.innerWidth || document.documentElement.clientWidth const h = window.innerHeight || document.documentElement.clientHeight return ( top <= h && (top + height) >= 0 && left <= w && (left + width) >= 0 ) }
??接下來在實現圖片懶加載的過程中,還需要注意一些小問題:
scroll 事件只有在滾動行為發生時,才會被觸發,這里需要手動加載一次首屏的圖片;
利用 addEventListener 注冊事件處理程序時,需要保存事件處理程序的引用,以便銷毀注冊的事件程序。
function LazyLoad (el, options) { if (!(this instanceof LazyLoad)) { return new LazyLoad(el) } this.setting = Object.assign({}, { src: "data-src", srcset: "data-srcset", selector: ".lazyload" }, options) if (typeof el === "string") { el = document.querySelectorAll(el) } this.images = Array.from(el) this.listener = this.loadImage() this.listener() this.initEvent() } LazyLoad.prototype = { loadImage () { return throttle(function () { let startIndex = 0 while (startIndex < this.images.length) { const image = this.images[startIndex] if (isElementInViewport(image)) { const src = image.getAttribute(this.setting.src) const srcset = image.getAttribute(this.setting.srcset) if (image.tagName.toLowerCase() === "img") { if (src) { image.src = src } if (srcset) { image.srcset = srcset } } else { image.style.backgroundImage = `url(${src})` } this.images.splice(startIndex, 1) continue } startIndex++ } if (!this.images.length) { this.destroy() } }).bind(this) }, initEvent () { window.addEventListener("scroll", this.listener, false) }, destroy () { window.removeEventListener("scroll", this.listener, false) this.images = null this.listener = null } }四、今生
??如今,Web為開發者提供了 IntersectionObserver 接口,它可以異步監聽目標元素與其祖先或視窗的交叉狀態,注意這個接口是異步的,它不隨著目標元素的滾動同步觸發,所以它并不會影響頁面的滾動性能。
??IntersectionObserver 構造函數接收兩個參數,回調函數以及配置項。
??配置項中的參數有以下三個:
root:所監聽對象的具體祖先元素,默認是 viewport ;
rootMargin:計算交叉狀態時,將 margin 附加到祖先元素上,從而有效的擴大或者縮小祖先元素判定區域;
threshold:設置一系列的閾值,當交叉狀態達到閾值時,會觸發回調函數。
??IntersectionObserver 實例執行回調函數時,會傳遞一個包含 IntersectionObserverEntry 對象的數組,該對象一共有七大屬性:
time:返回一個記錄從 IntersectionObserver 的時間原點到交叉被觸發的時間的時間戳;
target:目標元素;
rootBounds:祖先元素的矩形區域信息;
boundingClientRect:目標元素的矩形區域信息,與前面提到的 Element.getBoundingClientRect() 方法效果一致;
intersectionRect:祖先元素與目標元素相交區域信息;
intersectionRatio:返回intersectionRect 與 boundingClientRect 的比例值;
isIntersecting:目標元素是否與祖先元素相交。
??在此之前,還需要了解 IntersectionObserver 實例方法:
observe:開始監聽一個目標元素;
unobserve:停止監聽特定的元素;
disconnect:使 IntersectionObserver 對象停止監聽工作;
takeRecords:為所有監聽目標返回一個 IntersectionObserverEntry 對象數組并且停止監聽這些目標。
??以下為實現代碼:
function LazyLoad (images, options = {}) { if (!(this instanceof LazyLoad)) { return new LazyLoad(images, options) } this.setting = Object.assign({}, { src: "data-src", srcset: "data-srcset", selector: ".lazyload" }, options) this.images = images || document.querySelectorAll(this.setting.selector) this.observer = null this.init() } LazyLoad.prototype.init = function () { let self = this let observerConfig = { root: null, rootMargin: "0px", threshold: [0] } this.observer = new IntersectionObserver(entries => { entries.forEach(entry => { const target = entry.target if (entry.intersectionRatio > 0) { this.observer.unobserve(target) const src = target.getAttribute(this.setting.src) const srcset = target.getAttribute(this.setting.srcset) if ("img" === target.tagName.toLowerCase()) { if (src) { target.src = src } if (srcset) { target.srcset = srcset } } else { target.style.backgroundImage = `url(${src})` } } }) }, observerConfig) this.images.forEach(image => this.observer.observe(image)) }五、總結
??到此,實現圖片懶加載主要有兩種方法:
監聽 scroll 事件,通過 getBoundingClientRect() 計算目標元素與視口的交叉狀態;
IntersectionObserver 接口。
??第二種方法更加省心,到目前為止兼容性也還行:
??相比較下,第一種方法需要從各個方面去優化 scroll 事件,從而達到頁面滾動“如絲般順滑”的效果。
??最后,放上兩篇關于滾動優化的文章。
百度外賣前端 -- 如何不擇手段提升scroll事件的性能
高性能滾動 scroll 及頁面渲染優化
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102799.html
摘要:的最早版本誕生在年,這個最早版本和現在比起來極其簡單,沒有頭,沒有狀態碼,甚至版本號也沒有,后來它的版本號才被定為來和其他版本的區分。由于沒有狀態碼和錯誤代碼,如果服務器處理的時候發生錯誤,只會傳回一個特殊的包含問題描述信息的文件。 作為互聯網通信協議的一員老將,HTTP 協議走到今天已經經歷了三次版本的變動,現在最新的版本是 HTTP2.0,相信大家早已耳熟能詳。今天就給大家好好介紹...
摘要:前言我是,如果你還不認識我,不妨先看看技術的前世今生一平靜的生活已經有一段日子了。傳送門技術的前世今生一技術的前世今生三 前言:我是JavaScript,如果你還不認識我,不妨先看看《Web技術的前世今生(一)》 平靜的生活已經有一段日子了。 這一天,HTML大哥面露不悅地走過來問我: Js,你是打算和我們分家嗎? 大哥,您這說的哪里話,我什么地方做的不對么?我一臉茫然地回答道。 哼,...
摘要:前言我是,如果你還不認識我,不妨先看看技術的前世今生一平靜的生活已經有一段日子了。傳送門技術的前世今生一技術的前世今生三 前言:我是JavaScript,如果你還不認識我,不妨先看看《Web技術的前世今生(一)》 平靜的生活已經有一段日子了。 這一天,HTML大哥面露不悅地走過來問我: Js,你是打算和我們分家嗎? 大哥,您這說的哪里話,我什么地方做的不對么?我一臉茫然地回答道。 哼,...
閱讀 2280·2019-08-30 15:56
閱讀 3115·2019-08-30 13:48
閱讀 1128·2019-08-30 10:52
閱讀 1497·2019-08-29 17:30
閱讀 426·2019-08-29 13:44
閱讀 3547·2019-08-29 12:53
閱讀 1117·2019-08-29 11:05
閱讀 2671·2019-08-26 13:24