前端性能優(yōu)化之圖片懶加載技術(shù)3種落地實(shí)現(xiàn)方案
前言
在前端性能優(yōu)化體系中,圖片資源往往是頁(yè)面加載的“重災(zāi)區(qū)” ——電商列表、資訊長(zhǎng)文、相冊(cè)類(lèi)頁(yè)面,動(dòng)輒十幾張甚至上百?gòu)垐D片,若全量一次性加載,不僅拖慢首屏渲染、搶占帶寬,還會(huì)造成大量無(wú)效請(qǐng)求。
而圖片懶加載作為針對(duì)性極強(qiáng)的優(yōu)化手段,核心邏輯是非首屏圖片延遲加載,進(jìn)入可視區(qū)域再請(qǐng)求真實(shí)資源,既能大幅降低首屏加載耗時(shí),又能節(jié)省流量、提升頁(yè)面流暢度,更是優(yōu)化 LCP、CLS 等 Core Web Vitals 核心指標(biāo)的關(guān)鍵。
本文專(zhuān)注拆解圖片懶加載,從原理、適用場(chǎng)景、3種落地實(shí)現(xiàn)方案,到避坑指南、效果驗(yàn)證
一、先理清:圖片懶加載的核心原理
圖片懶加載沒(méi)有復(fù)雜底層邏輯,本質(zhì)是 “阻斷默認(rèn)加載 + 監(jiān)聽(tīng)可視狀態(tài) + 動(dòng)態(tài)替換資源” 的閉環(huán)流程,針對(duì)瀏覽器默認(rèn)自上而下加載圖片的機(jī)制做優(yōu)化:
- 標(biāo)記占位:不直接將圖片真實(shí)地址放入
src屬性(避免默認(rèn)加載),改用data-src等自定義屬性存儲(chǔ)真實(shí)地址,src填充占位圖(loading圖、純色占位、極小縮略圖); - 監(jiān)聽(tīng)狀態(tài):監(jiān)聽(tīng)頁(yè)面滾動(dòng)、元素位置,判斷圖片是否進(jìn)入瀏覽器可視區(qū)域;
- 加載資源:滿(mǎn)足可視條件后,將
data-src中的真實(shí)地址賦值給src,完成圖片加載,同時(shí)移除監(jiān)聽(tīng)避免重復(fù)執(zhí)行。
簡(jiǎn)單來(lái)說(shuō):先用占位圖“糊弄”瀏覽器,等用戶(hù)快看到圖片時(shí),再加載真實(shí)圖片。
二、圖片懶加載的適用場(chǎng)景
圖片懶加載并非所有場(chǎng)景都適用,精準(zhǔn)落地以下場(chǎng)景,優(yōu)化收益最大化:
- 長(zhǎng)頁(yè)面圖片列表:電商商品頁(yè)、資訊文章、瀑布流相冊(cè)、短視頻封面墻;
- 非首屏圖片:頁(yè)面底部、折疊面板、彈窗內(nèi)的圖片,用戶(hù)初始瀏覽不到的資源;
- 大體積圖片:高清banner、詳情圖、實(shí)拍圖,單張?bào)w積超過(guò)100KB的資源。
禁忌場(chǎng)景:首屏核心圖片(Logo、首屏banner、導(dǎo)航圖標(biāo))嚴(yán)禁懶加載,否則會(huì)惡化首屏渲染速度。
三、實(shí)戰(zhàn):圖片懶加載3種實(shí)現(xiàn)方案(從基礎(chǔ)到進(jìn)階)
針對(duì)不同項(xiàng)目兼容性、性能要求,整理3種最常用的圖片懶加載方案,覆蓋原生JS、瀏覽器原生API、HTML原生屬性,按需選擇即可。
方案1:原生JS + 滾動(dòng)監(jiān)聽(tīng) + getBoundingClientRect(兼容老瀏覽器)
這是最基礎(chǔ)、兼容性最強(qiáng)的方案,通過(guò)監(jiān)聽(tīng) scroll 滾動(dòng)事件,結(jié)合 getBoundingClientRect() 獲取圖片元素位置,判斷是否進(jìn)入視口,支持 IE 等老舊瀏覽器,適合老項(xiàng)目改造。
核心要點(diǎn):搭配節(jié)流函數(shù)優(yōu)化scroll 高頻觸發(fā)問(wèn)題,減少性能損耗。
完整代碼實(shí)現(xiàn)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>圖片懶加載-滾動(dòng)監(jiān)聽(tīng)版</title>
<style>
img {
width: 100%;
max-width: 800px;
/* 固定寬高比,防止布局偏移(CLS) */
aspect-ratio: 16/9;
object-fit: cover;
background: #f5f7fa;
margin: 20px auto;
display: block;
}
</style>
</head>
<body>
<!-- 懶加載圖片:data-src存真實(shí)地址,src為占位圖 -->
<img class="lazy-img" data-src="https://picsum.photos/800/450?1" src="loading.svg" alt="懶加載圖片1">
<img class="lazy-img" data-src="https://picsum.photos/800/450?2" src="loading.svg" alt="懶加載圖片2">
<img class="lazy-img" data-src="https://picsum.photos/800/450?3" src="loading.svg" alt="懶加載圖片3">
<img class="lazy-img" data-src="https://picsum.photos/800/450?4" src="loading.svg" alt="懶加載圖片4">
<script>
// 1. 獲取所有懶加載圖片
const lazyImages = document.querySelectorAll('.lazy-img');
// 2. 節(jié)流函數(shù):控制scroll觸發(fā)頻率,避免頻繁執(zhí)行
const throttle = (fn, delay = 200) => {
let timer = null;
return (...args) => {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
};
// 3. 核心:判斷圖片是否進(jìn)入可視區(qū)域
const lazyLoad = () => {
lazyImages.forEach((img) => {
// 獲取圖片相對(duì)于視口的位置信息
const rect = img.getBoundingClientRect();
// 判定條件:圖片頂部 ≤ 視口高度 且 圖片底部 ≥ 0(進(jìn)入可視區(qū)域)
const isInView = rect.top <= window.innerHeight && rect.bottom >= 0;
if (isInView) {
// 替換真實(shí)圖片地址
img.src = img.dataset.src;
// 加載失敗兜底圖
img.onerror = () => { img.src = 'error.svg'; };
// 移除懶加載類(lèi),避免重復(fù)處理
img.classList.remove('lazy-img');
}
});
};
// 初始化:加載首屏圖片
lazyLoad();
// 監(jiān)聽(tīng)滾動(dòng)事件(節(jié)流優(yōu)化)
window.addEventListener('scroll', throttle(lazyLoad));
// 監(jiān)聽(tīng)窗口縮放,適配不同屏幕
window.addEventListener('resize', throttle(lazyLoad));
</script>
</body>
</html>方案優(yōu)缺點(diǎn)
- ? 優(yōu)點(diǎn):兼容性拉滿(mǎn),邏輯簡(jiǎn)單,無(wú)需依賴(lài)第三方庫(kù),易調(diào)試;
- ? 缺點(diǎn):
scroll事件觸發(fā)頻率高,即使節(jié)流仍有一定性能損耗,需手動(dòng)處理邊界場(chǎng)景。
方案2:Intersection Observer API(現(xiàn)代瀏覽器首選)
Intersection Observer 是瀏覽器原生提供的異步交叉觀察器,專(zhuān)門(mén)用于監(jiān)聽(tīng)元素與視口(或父容器)的交叉狀態(tài),無(wú)需手動(dòng)監(jiān)聽(tīng)滾動(dòng)事件,由瀏覽器底層優(yōu)化,性能遠(yuǎn)超滾動(dòng)監(jiān)聽(tīng)方案,是目前主流的圖片懶加載實(shí)現(xiàn)方式。
核心優(yōu)勢(shì):異步執(zhí)行、無(wú)性能損耗、支持提前加載、配置靈活。
完整代碼實(shí)現(xiàn)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>圖片懶加載-Intersection Observer版</title>
<style>
img {
width: 100%;
max-width: 800px;
aspect-ratio: 16/9;
object-fit: cover;
background: #f5f7fa;
margin: 20px auto;
display: block;
}
</style>
</head>
<body>
<img class="lazy-img" data-src="https://picsum.photos/800/450?1" src="loading.svg" alt="懶加載圖片1">
<img class="lazy-img" data-src="https://picsum.photos/800/450?2" src="loading.svg" alt="懶加載圖片2">
<img class="lazy-img" data-src="https://picsum.photos/800/450?3" src="loading.svg" alt="懶加載圖片3">
<img class="lazy-img" data-src="https://picsum.photos/800/450?4" src="loading.svg" alt="懶加載圖片4">
<script>
// 1. 創(chuàng)建觀察器實(shí)例
const imgObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
// 判斷圖片是否進(jìn)入可視區(qū)域
if (entry.isIntersecting) {
const img = entry.target;
// 加載真實(shí)圖片
img.src = img.dataset.src;
// 錯(cuò)誤兜底
img.onerror = () => { img.src = 'error.svg'; };
// 取消觀察,避免重復(fù)觸發(fā)
observer.unobserve(img);
}
});
}, {
// 配置項(xiàng):提前10%視口高度觸發(fā),提升用戶(hù)體驗(yàn)
rootMargin: '10% 0px',
// 觸發(fā)閾值:0表示元素剛進(jìn)入視口就加載
threshold: 0
});
// 2. 遍歷所有圖片,開(kāi)啟觀察
document.querySelectorAll('.lazy-img').forEach(img => {
imgObserver.observe(img);
});
</script>
</body>
</html>關(guān)鍵配置項(xiàng)解析
root:監(jiān)聽(tīng)的根容器,默認(rèn)是瀏覽器視口,可指定父容器實(shí)現(xiàn)局部滾動(dòng)懶加載;rootMargin:擴(kuò)展觸發(fā)邊界,正值提前加載,負(fù)值延遲加載(例:10% 0px表示圖片距離視口底部10%高度時(shí)就加載);threshold:元素可見(jiàn)比例,取值0-1,0為剛可見(jiàn)就觸發(fā),1為完全可見(jiàn)才觸發(fā)。
方案優(yōu)缺點(diǎn)
- ? 優(yōu)點(diǎn):性能極致、代碼簡(jiǎn)潔、支持預(yù)加載、無(wú)需處理節(jié)流/縮放;
- ? 缺點(diǎn):不兼容 IE 瀏覽器,可引入 polyfill 做兼容處理。
方案3:HTML原生loading屬性(極簡(jiǎn)零代碼)
現(xiàn)代瀏覽器(Chrome 77+、Firefox 75+、Edge 79+)支持原生 loading="lazy" 屬性,無(wú)需編寫(xiě)任何 JS 代碼,直接在 img 標(biāo)簽添加該屬性,瀏覽器自動(dòng)實(shí)現(xiàn)圖片懶加載,是最簡(jiǎn)單、最輕量的方案。
適用場(chǎng)景:新項(xiàng)目、無(wú)需兼容老舊瀏覽器、追求極簡(jiǎn)開(kāi)發(fā)的場(chǎng)景。
代碼實(shí)現(xiàn)
<!-- 原生懶加載:僅需添加 loading="lazy" --> <img src="https://picsum.photos/800/450?1" loading="lazy" alt="原生懶加載圖片" width="800" height="450" > <img src="https://picsum.photos/800/450?2" loading="lazy" alt="原生懶加載圖片" width="800" height="450" >
注意事項(xiàng)
- 必須設(shè)置圖片
width和height,否則瀏覽器無(wú)法判斷布局,可能失效; - 首屏圖片不建議使用,瀏覽器可能會(huì)強(qiáng)制加載首屏內(nèi)的圖片;
- 兼容性有限,老舊瀏覽器會(huì)忽略該屬性,直接加載圖片(優(yōu)雅降級(jí))。
四、圖片懶加載避坑指南(實(shí)戰(zhàn)必看)
實(shí)操圖片懶加載時(shí),這幾個(gè)坑極易忽略,直接影響用戶(hù)體驗(yàn)和性能指標(biāo):
1. 嚴(yán)防布局偏移(CLS)
這是最常見(jiàn)問(wèn)題:圖片未加載時(shí)無(wú)固定占位高度,加載后撐開(kāi)頁(yè)面導(dǎo)致布局抖動(dòng),CLS指標(biāo)超標(biāo)。
解決方案:通過(guò) CSS aspect-ratio 固定寬高比,或提前設(shè)置 width/height,預(yù)留圖片空間。
2. 占位圖優(yōu)化
- 占位圖體積盡量?。ńㄗh<2KB),推薦使用 SVG 占位圖、純色背景或 Base64 縮略圖;
- 避免使用高清圖做占位,失去懶加載意義。
3. 圖片加載失敗兜底
網(wǎng)絡(luò)異常、圖片地址失效會(huì)導(dǎo)致圖片加載失敗,需通過(guò) onerror 事件替換兜底圖,提升體驗(yàn)。
4. 及時(shí)銷(xiāo)毀監(jiān)聽(tīng)/觀察器
JS 實(shí)現(xiàn)的懶加載,圖片加載完成后務(wù)必移除滾動(dòng)監(jiān)聽(tīng)、取消 Intersection Observer 觀察,防止內(nèi)存泄漏。
5. 兼容禁用JS場(chǎng)景
部分用戶(hù)禁用瀏覽器 JS,會(huì)導(dǎo)致圖片無(wú)法加載,通過(guò) <noscript> 標(biāo)簽兜底。
<img class="lazy-img" data-src="real.jpg" src="loading.svg" alt="圖片"> <!-- 禁用JS時(shí)直接加載真實(shí)圖片 --> <noscript> <img src="real.jpg" alt="圖片" width="800" height="450"> </noscript>
五、優(yōu)化效果驗(yàn)證工具
圖片懶加載落地后,通過(guò)以下工具驗(yàn)證優(yōu)化效果:
- Chrome 開(kāi)發(fā)者工具:打開(kāi) Network 面板,篩選 Img,滾動(dòng)頁(yè)面觀察圖片請(qǐng)求是否延遲觸發(fā);
- Lighthouse:生成性能報(bào)告,查看 LCP(最大內(nèi)容繪制)、CLS(累積布局偏移)指標(biāo)是否優(yōu)化;
- Performance 面板:查看首屏加載耗時(shí)、頁(yè)面渲染幀率是否提升。
六、工程化進(jìn)階:懶加載指令封裝+主流插件實(shí)戰(zhàn)
實(shí)際項(xiàng)目開(kāi)發(fā)中,重復(fù)手寫(xiě)懶加載邏輯效率太低,更推薦封裝復(fù)用指令/Hooks或直接使用成熟插件,適配Vue、React等框架工程化場(chǎng)景,下面附上可直接復(fù)用的封裝代碼和插件用法。
6.1 Vue3 圖片懶加載自定義指令(全局封裝)
基于 Intersection Observer 封裝全局懶加載指令,一鍵復(fù)用,無(wú)需重復(fù)寫(xiě)監(jiān)聽(tīng)邏輯,適配Vue3項(xiàng)目。
步驟1:創(chuàng)建指令文件(directives/lazyLoad.js)
// 全局圖片懶加載指令
const lazyLoad = {
mounted(el, binding) {
// 初始化占位圖
el.src = 'loading.svg';
// 創(chuàng)建觀察器
const observer = new IntersectionObserver((entries) => {
const [entry] = entries;
if (entry.isIntersecting) {
// 加載真實(shí)圖片
el.src = binding.value;
// 加載失敗兜底
el.onerror = () => { el.src = 'error.svg'; };
// 停止觀察
observer.unobserve(el);
}
}, { rootMargin: '10% 0px' });
// 綁定觀察對(duì)象
observer.observe(el);
// 存儲(chǔ)觀察器,用于卸載
el._observer = observer;
},
// 指令卸載時(shí)銷(xiāo)毀觀察器
unmounted(el) {
el._observer?.unobserve(el);
}
};
export default lazyLoad;步驟2:全局注冊(cè)指令(main.js)
import { createApp } from 'vue';
import App from './App.vue';
import lazyLoad from './directives/lazyLoad';
const app = createApp(App);
// 注冊(cè)全局指令 v-lazy
app.directive('lazy', lazyLoad);
app.mount('#app');步驟3:頁(yè)面使用
<!-- 直接使用 v-lazy 指令,傳入真實(shí)圖片地址 --> <img v-lazy="https:/picsum.photos800/450?1" alt="指令懶加載" /
6.2 React 圖片懶加載 Hooks 封裝
封裝自定義Hook,實(shí)現(xiàn)React函數(shù)組件復(fù)用,適配React項(xiàng)目。
import { useEffect, useRef } from 'react';
// 自定義懶加載Hook
function useLazyImg() {
const imgRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
const [entry] = entries;
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.onerror = () => { img.src = 'error.svg'; };
observer.unobserve(img);
}
}, { rootMargin: '10% 0px' });
if (imgRef.current) observer.observe(imgRef.current);
return () => {
if (imgRef.current) observer.unobserve(imgRef.current);
};
}, []);
return imgRef;
}
// 組件使用
export default function LazyImage({ src, alt }) {
const imgRef = useLazyImg();
return (
<img
ref={src}
src="loading.svg"
alt={alt}
style={{ width: '100%', aspectRatio: '16/9' }}
/>
);
}6.3 主流懶加載插件推薦(開(kāi)箱即用)
不想手動(dòng)封裝,可直接使用社區(qū)成熟插件,配置簡(jiǎn)單、功能完善。
Vue2/Vue3:vue-lazyload
Vue生態(tài)最常用的圖片懶加載插件,支持占位圖、加載失敗、節(jié)流等功能。
# 安裝 npm install vue-lazyload --save
// 注冊(cè)(main.js)
import Vue from 'vue';
import VueLazyload from 'vue-lazyload';
Vue.use(VueLazyload, {
preLoad: 1.3, // 提前加載比例
error: 'error.svg', // 失敗圖
loading: 'loading.svg', // 占位圖
attempt: 1 // 加載次數(shù)
});
// 頁(yè)面使用React:react-lazy-load-image-component
React專(zhuān)用懶加載組件,支持淡入動(dòng)畫(huà)、占位、響應(yīng)式,適配SSR場(chǎng)景。
# 安裝 npm install react-lazy-load-image-component --save
import { LazyLoadImage } from 'react-lazy-load-image-component';
import 'react-lazy-load-image-component/src/effects/blur.css';
// 使用
function ImageList() {
return (
<LazyLoadImage
src="https://picsum.photos/800/450"
alt="插件懶加載"
effect="blur" // 淡入模糊效果
placeholderSrc="loading.svg" // 占位圖
width="100%"
/>
);
}七、方案選型總結(jié)
- 原生開(kāi)發(fā)/老項(xiàng)目:選滾動(dòng)監(jiān)聽(tīng) + getBoundingClientRect 方案;
- 現(xiàn)代瀏覽器/追求性能:選Intersection Observer API 方案(首選);
- 極簡(jiǎn)開(kāi)發(fā)/零代碼:選原生 loading="lazy" 屬性;
- Vue/React工程化:優(yōu)先用自定義指令/Hooks,快速?gòu)?fù)用;
- 快速落地:直接用 vue-lazyload/react-lazy-load-image-component 插件。
到此這篇關(guān)于前端性能優(yōu)化之圖片懶加載技術(shù)3種落地實(shí)現(xiàn)方案的文章就介紹到這了,更多相關(guān)前端圖片懶加載技術(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
element 中 el-menu 組件的無(wú)限極循環(huán)思路代碼詳解
這篇文章主要介紹了element 中 el-menu 組件的無(wú)限極循環(huán),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
javascript關(guān)于open.window子頁(yè)面執(zhí)行完成后刷新父頁(yè)面的問(wèn)題分析
這篇文章主要介紹了javascript關(guān)于open.window子頁(yè)面執(zhí)行完成后刷新父頁(yè)面的問(wèn)題,實(shí)例分析了javascript操作子頁(yè)面的執(zhí)行與父頁(yè)面的刷新技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
小程序拖動(dòng)區(qū)域?qū)崿F(xiàn)排序效果
這篇文章主要為大家詳細(xì)介紹了小程序拖動(dòng)區(qū)域?qū)崿F(xiàn)排序效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09
js實(shí)現(xiàn)百度聯(lián)盟中一款不錯(cuò)的圖片切換效果完整實(shí)例
這篇文章主要介紹了js實(shí)現(xiàn)百度聯(lián)盟中一款不錯(cuò)的圖片切換效果的方法,以完整實(shí)例形式分析了javascript操作圖片切換的技巧,需要的朋友可以參考下2015-03-03
javascript刪除數(shù)組元素的七個(gè)方法示例
這篇文章主要給大家介紹了關(guān)于javascript刪除數(shù)組元素的七個(gè)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用javascript具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
史上最全JavaScript常用的簡(jiǎn)寫(xiě)技巧(推薦)
這篇文章主要介紹了JavaScript常用的簡(jiǎn)寫(xiě)技巧,列舉了20條js中常用的簡(jiǎn)寫(xiě)技巧,具體操作步驟大家可查看下文的詳細(xì)講解,感興趣的小伙伴們可以參考一下2017-08-08

