懶加載
什么是懶加載
懶加載其實(shí)就是延遲加載淑翼,是一種對網(wǎng)頁性能優(yōu)化的方式,比如當(dāng)訪問一個(gè)頁面的時(shí)候爱咬,優(yōu)先顯示可視區(qū)域的圖片而不一次性加載所有圖片,當(dāng)需要顯示的時(shí)候再發(fā)送圖片請求驾讲,避免打開網(wǎng)頁時(shí)加載過多資源。
什么時(shí)候用懶加載
當(dāng)頁面中需要一次性載入很多圖片的時(shí)候世曾,往往都是需要用懶加載的辖佣。
懶加載原理
我們都知道HTML中的 <img>
標(biāo)簽是代表文檔中的一個(gè)圖像霹抛。。說了個(gè)廢話卷谈。杯拐。
<img>
標(biāo)簽有一個(gè)屬性是 src
,用來表示圖像的URL世蔗,當(dāng)這個(gè)屬性的值不為空時(shí)端逼,瀏覽器就會(huì)根據(jù)這個(gè)值發(fā)送請求。如果沒有 src
屬性污淋,就不會(huì)發(fā)送請求顶滩。
嗯?貌似這點(diǎn)可以利用一下寸爆?
我先不設(shè)置 src
礁鲁,需要的時(shí)候再設(shè)置?
nice赁豆,就是這樣仅醇。
我們先不給 <img>
設(shè)置 src
,把圖片真正的URL放在另一個(gè)屬性 data-src
中魔种,在需要的時(shí)候也就是圖片進(jìn)入可視區(qū)域的之前析二,將URL取出放到 src
中。
實(shí)現(xiàn)
HTML結(jié)構(gòu)
<div class="container">
?<div class="img-area">
? ?<img class="my-photo" alt="loading" src="./img/img1.png">
?</div>
?<div class="img-area">
? ?<img class="my-photo" alt="loading" src="./img/img2.png">
?</div>
?<div class="img-area">
? ?<img class="my-photo" alt="loading" src="./img/img3.png">
?</div>
?<div class="img-area">
? ?<img class="my-photo" alt="loading" src="./img/img4.png">
?</div>
?<div class="img-area">
? ?<img class="my-photo" alt="loading" src="./img/img5.png">
?</div>
</div>
仔細(xì)觀察一下节预, <img>
標(biāo)簽此時(shí)是沒有 src
屬性的叶摄,只有 alt
和 data-src
屬性属韧。
alt 屬性是一個(gè)必需的屬性,它規(guī)定在圖像無法顯示時(shí)的替代文本准谚。 data-* 全局屬性:構(gòu)成一類名稱為自定義數(shù)據(jù)屬性的屬性挫剑,可以通過
HTMLElement.dataset
來訪問。
如何判斷元素是否在可視區(qū)域
方法一
網(wǎng)上看到好多這種方法柱衔,稍微記錄一下樊破。
通過?document.documentElement.clientHeight
獲取屏幕可視窗口高度
通過?document.documentElement.scrollTop
獲取瀏覽器窗口頂部與文檔頂部之間的距離,也就是滾動(dòng)條滾動(dòng)的距離
通過?element.offsetTop
獲取元素相對于文檔頂部的距離
然后判斷②-③<①是否成立唆铐,如果成立哲戚,元素就在可視區(qū)域內(nèi)。
方法二(推薦)
通過 getBoundingClientRect()
方法來獲取元素的大小以及位置艾岂,MDN上是這樣描述的:
The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.
這個(gè)方法返回一個(gè)名為 ClientRect
的 DOMRect
對象顺少,包含了 top
、 right
王浴、 botton
脆炎、 left
、 width
氓辣、 height
這些值秒裕。
MDN上有這樣一張圖:
可以看出返回的元素位置是相對于左上角而言的,而不是邊距钞啸。
我們思考一下几蜻,什么情況下圖片進(jìn)入可視區(qū)域。
假設(shè) constbound=el.getBoundingClientRect();
來表示圖片到可視區(qū)域頂部距離体斩; 并設(shè) constclientHeight=window.innerHeight;
來表示可視區(qū)域的高度梭稚。
隨著滾動(dòng)條的向下滾動(dòng), bound.top
會(huì)越來越小絮吵,也就是圖片到可視區(qū)域頂部的距離越來越小弧烤,當(dāng) bound.top===clientHeight
時(shí),圖片的上沿應(yīng)該是位于可視區(qū)域下沿的位置的臨界點(diǎn)源武,再滾動(dòng)一點(diǎn)點(diǎn)扼褪,圖片就會(huì)進(jìn)入可視區(qū)域。
也就是說粱栖,在 bound.top<=clientHeight
時(shí)话浇,圖片是在可視區(qū)域內(nèi)的。
我們這樣判斷:
function isInSight(el) {
?const bound = el.getBoundingClientRect();
?const clientHeight = window.innerHeight;
?//如果只考慮向下滾動(dòng)加載
?//const clientWidth = window.innerWeight;
?return bound.top <= clientHeight + 100;
}
這里有個(gè)+100是為了提前加載闹究。
加載圖片
頁面打開時(shí)需要對所有圖片進(jìn)行檢查幔崖,是否在可視區(qū)域內(nèi),如果是就加載。
function checkImgs() {
?const imgs = document.querySelectorAll('.my-photo');
?Array.from(imgs).forEach(el => {
? ?if (isInSight(el)) {
? ? ?loadImg(el);
? ?}
?})
}
function loadImg(el) {
?if (!el.src) {
? ?const source = el.dataset.src;
? ?el.src = source;
?}
}
這里應(yīng)該是有一個(gè)優(yōu)化的地方赏寇,設(shè)一個(gè)標(biāo)識符標(biāo)識已經(jīng)加載圖片的index吉嫩,當(dāng)滾動(dòng)條滾動(dòng)時(shí)就不需要遍歷所有的圖片,只需要遍歷未加載的圖片即可嗅定。
函數(shù)節(jié)流
在類似于滾動(dòng)條滾動(dòng)等頻繁的DOM操作時(shí)自娩,總會(huì)提到“函數(shù)節(jié)流、函數(shù)去抖”渠退。
所謂的函數(shù)節(jié)流忙迁,也就是讓一個(gè)函數(shù)不要執(zhí)行的太頻繁,減少一些過快的調(diào)用來節(jié)流碎乃。
基本步驟:
獲取第一次觸發(fā)事件的時(shí)間戳
獲取第二次觸發(fā)事件的時(shí)間戳
時(shí)間差如果大于某個(gè)閾值就執(zhí)行事件姊扔,然后重置第一個(gè)時(shí)間
function throttle(fn, mustRun = 500) {
?const timer = null;
?let previous = null;
?return function() {
? ?const now = new Date();
? ?const context = this;
? ?const args = arguments;
? ?if (!previous){
? ? ?previous = now;
? ?}
? ?const remaining = now - previous;
? ?if (mustRun && remaining >= mustRun) {
? ? ?fn.apply(context, args);
? ? ?previous = now;
? ?}
?}
}
這里的 mustRun
就是調(diào)用函數(shù)的時(shí)間間隔,無論多么頻繁的調(diào)用 fn
梅誓,只有 remaining>=mustRun
時(shí) fn
才能被執(zhí)行恰梢。
實(shí)驗(yàn)
頁面打開時(shí)
可以看出此時(shí)僅僅是加載了img1和img2,其它的img都沒發(fā)送請求梗掰,看看此時(shí)的瀏覽器
第一張圖片是完整的呈現(xiàn)了嵌言,第二張圖片剛進(jìn)入可視區(qū)域,后面的就看不到了~
頁面滾動(dòng)時(shí)
當(dāng)我向下滾動(dòng)及穗,此時(shí)瀏覽器是這樣
此時(shí)第二張圖片完全顯示了呀页,而第三張圖片顯示了一點(diǎn)點(diǎn),這時(shí)候我們看看請求情況
img3的請求發(fā)出來拥坛,而后面的請求還是沒發(fā)出~
全部載入時(shí)
當(dāng)滾動(dòng)條滾到最底下時(shí),全部請求都應(yīng)該是發(fā)出的尘分,如圖
更新
方法三 IntersectionObserver
經(jīng)大佬提醒猜惋,發(fā)現(xiàn)了這個(gè)方法
先附上鏈接:
jjc大大:
https://github.com/justjavac/the-front-end-knowledge-you-may-dont-know/issues/10
阮一峰大大:
http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html
API Sketch for Intersection Observers:
https://github.com/WICG/IntersectionObserver
IntersectionObserver
可以自動(dòng)觀察元素是否在視口內(nèi)。
var io = new IntersectionObserver(callback, option);
// 開始觀察
io.observe(document.getElementById('example'));
// 停止觀察
io.unobserve(element);
// 關(guān)閉觀察器
io.disconnect();
callback的參數(shù)是一個(gè)數(shù)組培愁,每個(gè)數(shù)組都是一個(gè) IntersectionObserverEntry
對象著摔,包括以下屬性:
屬性描述time可見性發(fā)生變化的時(shí)間,單位為毫秒rootBounds與getBoundingClientRect()方法的返回值一樣boundingClientRect目標(biāo)元素的矩形區(qū)域的信息intersectionRect目標(biāo)元素與視口(或根元素)的交叉區(qū)域的信息intersectionRatio目標(biāo)元素的可見比例定续,即intersectionRect占boundingClientRect的比例谍咆,完全可見時(shí)為1,完全不可見時(shí)小于等于0target被觀察的目標(biāo)元素私股,是一個(gè) DOM 節(jié)點(diǎn)對象
我們需要用到 intersectionRatio
來判斷是否在可視區(qū)域內(nèi)摹察,當(dāng) intersectionRatio>0&&intersectionRatio<=1
即在可視區(qū)域內(nèi)。
代碼
function checkImgs() {
?const imgs = Array.from(document.querySelectorAll(".my-photo"));
?imgs.forEach(item => io.observe(item));
}
function loadImg(el) {
?if (!el.src) {
? ?const source = el.dataset.src;
? ?el.src = source;
?}
}
const io = new IntersectionObserver(ioes => {
?ioes.forEach(ioe => {
? ?const el = ioe.target;
? ?const intersectionRatio = ioe.intersectionRatio;
? ?if (intersectionRatio > 0 && intersectionRatio <= 1) {
? ? ?loadImg(el);
? ?}
? ?el.onload = el.onerror = () => io.unobserve(el);
?});
});
感興趣的小伙伴倡鲸,可以關(guān)注公眾號【grain先森】供嚎,回復(fù)關(guān)鍵詞 “vue”,獲取更多資料,更多關(guān)鍵詞玩法期待你的探索~