轉(zhuǎn)載地址:https://segmentfault.com/a/1190000010744417
懶加載
什么是懶加載
懶加載其實(shí)就是延遲加載,是一種對(duì)網(wǎng)頁(yè)性能優(yōu)化的方式,比如當(dāng)訪問(wèn)一個(gè)頁(yè)面的時(shí)候翘县,優(yōu)先顯示可視區(qū)域的圖片而不一次性加載所有圖片杀饵,當(dāng)需要顯示的時(shí)候再發(fā)送圖片請(qǐng)求系吭,避免打開(kāi)網(wǎng)頁(yè)時(shí)加載過(guò)多資源脉漏。
什么時(shí)候用懶加載
當(dāng)頁(yè)面中需要一次性載入很多圖片的時(shí)候苞冯,往往都是需要用懶加載的。
懶加載原理
我們都知道HTML中的<img>標(biāo)簽是代表文檔中的一個(gè)圖像鸠删。抱完。說(shuō)了個(gè)廢話。刃泡。
<img>標(biāo)簽有一個(gè)屬性是src巧娱,用來(lái)表示圖像的URL,當(dāng)這個(gè)屬性的值不為空時(shí)烘贴,瀏覽器就會(huì)根據(jù)這個(gè)值發(fā)送請(qǐng)求禁添。如果沒(méi)有src屬性,就不會(huì)發(fā)送請(qǐng)求桨踪。
嗯老翘?貌似這點(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/img1.png)
</div>
<div class="img-area">
![](./img/img2.png)
</div>
<div class="img-area">
![](./img/img3.png)
</div>
<div class="img-area">
![](./img/img4.png)
</div>
<div class="img-area">
![](./img/img5.png)
</div>
</div>
仔細(xì)觀察一下,<img>標(biāo)簽此時(shí)是沒(méi)有src屬性的碴犬,只有alt和data-src屬性絮宁。
alt 屬性是一個(gè)必需的屬性,它規(guī)定在圖像無(wú)法顯示時(shí)的替代文本服协。
data-* 全局屬性:構(gòu)成一類名稱為自定義數(shù)據(jù)屬性的屬性绍昂,可以通過(guò)HTMLElement.dataset來(lái)訪問(wèn)。
如何判斷元素是否在可視區(qū)域
方法一
網(wǎng)上看到好多這種方法偿荷,稍微記錄一下治专。
通過(guò)document.documentElement.clientHeight
獲取屏幕可視窗口高度
通過(guò)element.offsetTop
獲取元素相對(duì)于文檔頂部的距離
通過(guò)document.documentElement.scrollTop
獲取瀏覽器窗口頂部與文檔頂部之間的距離,也就是滾動(dòng)條滾動(dòng)的距離
然后判斷②-③<①是否成立遭顶,如果成立张峰,元素就在可視區(qū)域內(nèi)。
方法二(推薦)
通過(guò)getBoundingClientRect()
方法來(lái)獲取元素的大小以及位置棒旗,MDN上是這樣描述的:
The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.
這個(gè)方法返回一個(gè)名為ClientRect
的DOMRect
對(duì)象喘批,包含了top
撩荣、right
、botton
饶深、left
餐曹、width
、height
這些值敌厘。
MDN上有這樣一張圖:
可以看出返回的元素位置是相對(duì)于左上角而言的台猴,而不是邊距。
我們思考一下俱两,什么情況下圖片進(jìn)入可視區(qū)域饱狂。
假設(shè)const bound = el.getBoundingClientRect();
來(lái)表示圖片到可視區(qū)域頂部距離;并設(shè) const clientHeight = window.innerHeight;
來(lái)表示可視區(qū)域的高度宪彩。
隨著滾動(dòng)條的向下滾動(dòng)休讳,bound.top
會(huì)越來(lái)越小,也就是圖片到可視區(qū)域頂部的距離越來(lái)越小尿孔,當(dāng)bound.top===clientHeight
時(shí)俊柔,圖片的上沿應(yīng)該是位于可視區(qū)域下沿的位置的臨界點(diǎn),再滾動(dòng)一點(diǎn)點(diǎn)活合,圖片就會(huì)進(jìn)入可視區(qū)域雏婶。
也就是說(shuō),在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是為了提前加載。
經(jīng)提醒侵续。。這個(gè)方法性能
加載圖片
頁(yè)面打開(kāi)時(shí)需要對(duì)所有圖片進(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)識(shí)符標(biāo)識(shí)已經(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í)行的太頻繁,減少一些過(guò)快的調(diào)用來(lái)節(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í)間間隔,無(wú)論多么頻繁的調(diào)用fn,只有remaining>=mustRun時(shí)fn才能被執(zhí)行闯狱。
方法三 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);
// 開(kāi)始觀察
io.observe(document.getElementById('example'));
// 停止觀察
io.unobserve(element);
// 關(guān)閉觀察器
io.disconnect();
callback的參數(shù)是一個(gè)數(shù)組哄孤,每個(gè)數(shù)組都是一個(gè)IntersectionObserverEntry對(duì)象照筑,包括以下屬性:
屬性 描述
time 可見(jiàn)性發(fā)生變化的時(shí)間,單位為毫秒
rootBounds 與getBoundingClientRect()方法的返回值一樣
boundingClientRect 目標(biāo)元素的矩形區(qū)域的信息
intersectionRect 目標(biāo)元素與視口(或根元素)的交叉區(qū)域的信息
intersectionRatio 目標(biāo)元素的可見(jiàn)比例瘦陈,即intersectionRect占boundingClientRect的比例凝危,完全可見(jiàn)時(shí)為1,完全不可見(jiàn)時(shí)小于等于0
target 被觀察的目標(biāo)元素双饥,是一個(gè) DOM 節(jié)點(diǎn)對(duì)象
我們需要用到intersectionRatio來(lái)判斷是否在可視區(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);
});
});