前端實現(xiàn)圖片懶加載的三種手寫方案詳解
前言
在電商、社交等圖片密集型應(yīng)用中,一次性加載所有圖片會導(dǎo)致首屏白屏?xí)r間(FP)過長,消耗用戶大量流量。圖片懶加載的核心思想就是: “按需加載” ——只有當(dāng)圖片進(jìn)入或即將進(jìn)入可視區(qū)域時,才真正發(fā)起網(wǎng)絡(luò)請求。
一、 核心原理
- 占位圖:初始化時,圖片的
src屬性指向一張極小的 base64 圖片或 loading 占位圖。 - 存儲地址:將真實的圖片 URL 存放在自定義屬性中(如
data-src)。 - 觸發(fā)判斷:通過 JS 監(jiān)聽位置變化,當(dāng)圖片進(jìn)入可視區(qū),將
data-src的值賦給src。
二、 方案對比與實現(xiàn)
方案 1:傳統(tǒng)滾動監(jiān)聽(Scroll + OffsetTop)
這是最基礎(chǔ)的方案,通過計算絕對位置來判斷。
公式: window.innerHeight (可視窗口高) + document.documentElement.scrollTop (滾動條高度) > element.offsetTop (元素距離頁面頂部高度)
代碼實現(xiàn):
function lazyLoad() {
const images = document.querySelectorAll('img[data-src]');
const clientHeight = window.innerHeight;
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
images.forEach(img => {
// 判斷是否進(jìn)入可視區(qū)
if (clientHeight + scrollTop > img.offsetTop) {
img.src = img.dataset.src;
img.removeAttribute('data-src'); // 加載后移除屬性,防止重復(fù)執(zhí)行
}
});
}
// 注意:必須添加節(jié)流(Throttle),防止?jié)L動時高頻觸發(fā)導(dǎo)致卡頓
window.addEventListener('scroll', throttle(lazyLoad, 200));
方案 2:現(xiàn)代位置屬性(getBoundingClientRect)
此方法通過getBoundingClientRect獲取元素相對于瀏覽器視口的位置來判斷,邏輯更簡潔。
判斷條件: rect.top(元素頂部距離視口頂部的距離) < window.innerHeight。
代碼實現(xiàn):
function handleScroll() {
const img = document.getElementById('target-img');
const rect = img.getBoundingClientRect();
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
// 元素頂部出現(xiàn)在視口內(nèi),且沒有超出視口底部
if (rect.top >= 0 && rect.top < viewHeight) {
console.log('圖片進(jìn)入可視區(qū),開始加載');
img.src = img.dataset.src;
window.removeEventListener('scroll', handleScroll); // 加載后卸載監(jiān)聽
}
}
window.addEventListener('scroll', handleScroll);
方案 3:最優(yōu)解方案(IntersectionObserver API)
IntersectionObserver這是目前最推薦的方案,它是異步的,不會阻塞主線程,且不需要手動計算位置,性能最高。
使用語法:const observer = new IntersectionObserver(callback, options)
callback:是元素可見性發(fā)生變化時的回調(diào)函數(shù),接收兩個參數(shù):
entries:觀察目標(biāo)的對象數(shù)組。對象中存在isIntersecting屬性(布爾值),代表目標(biāo)元素是否與根元素交叉(即進(jìn)入視口)。
options:配置對象(該參數(shù)可選)。其中 root表示指定一個根元素,默認(rèn)是瀏覽器窗口、rootMargin表示控制根元素的外邊距、threshold 為目標(biāo)元素與根元素中的可見比例,可以通過設(shè)置值來觸發(fā)回調(diào)函數(shù)
代碼實現(xiàn):
const observerOptions = {
root: null, // 默認(rèn)為瀏覽器視口
rootMargin: '0px 0px 50px 0px', // 提前 50px 觸發(fā)加載,提升用戶體驗
threshold: 0.1 // 交叉比例達(dá)到 10% 時觸發(fā)
};
const handleIntersection = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
console.log('IntersectionObserver 推送:圖片加載成功');
observer.unobserve(img); // 停止觀察該元素
}
});
};
const observer = new IntersectionObserver(handleIntersection, observerOptions);
// 觀察頁面中所有帶 data-src 的圖片
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
方案 1 和 2 在獲取 offsetTop 或 getBoundingClientRect 時會強制瀏覽器重新計算布局(回流),在長列表場景下可能導(dǎo)致掉幀。方案 3 不受此影響
三、總結(jié)
| 特性 | 方案 1 (OffsetTop) | 方案 2 (Rect) | 方案 3 (Intersection) |
|---|---|---|---|
| 計算復(fù)雜度 | 較高 (需計算累計高度) | 中等 | 極低 (引擎原生實現(xiàn)) |
| 性能消耗 | 高 (頻繁觸發(fā)回流) | 高 (觸發(fā)回流) | 低 (異步非阻塞) |
| 兼容性 | 極好 (所有瀏覽器) | 好 (IE9+) | 一般 (現(xiàn)代瀏覽器, IE 需 Polyfill) |
四、補充
現(xiàn)代瀏覽器(Chrome 76+)已原生支持 <img loading="lazy">,如果是簡單場景,一行 HTML 屬性即可搞定。
務(wù)必給圖片設(shè)置固定的寬高比或底色占位。否則圖片加載前高度為 0,加載瞬間高度撐開會引發(fā)劇烈的頁面抖動。
到此這篇關(guān)于前端實現(xiàn)圖片懶加載的三種手寫方案詳解的文章就介紹到這了,更多相關(guān)前端圖片懶加載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
HTML+JavaScript實現(xiàn)筋斗云導(dǎo)航欄效果
這篇文章主要為大家介紹了如何利用HTML+JavaScript+CSS實現(xiàn)筋斗云導(dǎo)航欄效果,當(dāng)鼠標(biāo)經(jīng)過某個li,筋斗云跟著到當(dāng)前的位置,感興趣的小伙伴可以試一試2022-03-03
JS實現(xiàn)獲取word文檔內(nèi)容并輸出顯示到html頁面示例
這篇文章主要介紹了JS實現(xiàn)獲取word文檔內(nèi)容并輸出顯示到html頁面,結(jié)合實例形式分析了JavaScript使用ActiveXObject組建操作word文件的相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2018-06-06
Ajax實現(xiàn)省市區(qū)三級聯(lián)動實例代碼
這篇文章介紹了Ajax實現(xiàn)省市區(qū)三級聯(lián)動的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-04-04

