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

