懶加載(Lazy Loading)一直是前端性能優(yōu)化的常見(jiàn)方式。不知道從什么時(shí)候開(kāi)始 <img loading="lazy">
這樣的屬性方案開(kāi)始大行其道,很多同學(xué)并不了解 <img loading="lazy">
的作用,就直接把它放到項(xiàng)目里進(jìn)行使用,不知不覺(jué)就為自己埋下了大坑!
loading="lazy" 的作用

根據(jù) MDN
的介紹,我們可以很清楚的了解 <img loading="lazy">
的作用, 很多同學(xué)在看到這個(gè)屬性后都會(huì)“虎軀一震”,覺(jué)得:“這簡(jiǎn)直是完美解決懶加載的方案啊,不用寫(xiě) JS 了,真香!”
但現(xiàn)實(shí)遠(yuǎn)沒(méi)有那么理想:瀏覽器的懶加載行為遠(yuǎn)不如你想象中的那么“聰明”:
1. 加載觸發(fā)點(diǎn)不穩(wěn)定,不可控
瀏覽器內(nèi)部定義了一個(gè)“可見(jiàn)區(qū)域附近的預(yù)加載區(qū)域”(preload margin),當(dāng)圖片即將進(jìn)入視口時(shí)才加載。
但這個(gè)區(qū)域是 不可控的!不同瀏覽器、不同版本、甚至不同網(wǎng)絡(luò)環(huán)境下的觸發(fā)條件都不一樣!
舉個(gè)例子:
- 在 Chrome 中可能是距離視口下方 1250px 就開(kāi)始加載。
- 在 Safari 中可能必須接近視口 200px 才觸發(fā)。
所以你可能會(huì)發(fā)現(xiàn)用戶(hù)已經(jīng)快滾到圖片了,但圖片還沒(méi)開(kāi)始加載,造成“白屏”或者“圖片加載卡頓” 的問(wèn)題
2. 首屏關(guān)鍵圖被延遲加載
很多同學(xué)直接在所有 <img>
標(biāo)簽上加上 loading="lazy"
,包括首屏 Banner、Logo、首圖等關(guān)鍵視覺(jué)內(nèi)容。
這是個(gè)大坑!
首屏圖片本應(yīng)盡早加載,提升 FCP(首次內(nèi)容繪制)性能,但 loading="lazy"
會(huì)把它推遲,導(dǎo)致頁(yè)面看起來(lái)很慢。
比如,如下代碼就會(huì)導(dǎo)致 FCP 時(shí)間變長(zhǎng)、LCP 分?jǐn)?shù)下降、用戶(hù)體驗(yàn)變差
<!-- 下面的 Logo 是首屏元素,卻用了 lazy,導(dǎo)致 Logo 顯示很慢 -->
<img src="/logo.png" loading="lazy" alt="品牌Logo" />
3. 加載狀態(tài)不可監(jiān)聽(tīng)
在實(shí)際開(kāi)發(fā)中,我們通常需要 監(jiān)聽(tīng)圖片懶加載的狀態(tài),比如:增加占位圖
各種色塊的占位圖
但是,loading="lazy"
不可監(jiān)聽(tīng)!這就會(huì)導(dǎo)致以下功能完全無(wú)法實(shí)現(xiàn):
- 沒(méi)法給圖片加載過(guò)程加動(dòng)畫(huà)(比如漸現(xiàn))
- 沒(méi)法給圖片加載失敗加容錯(cuò)處理(比如加載失敗替換默認(rèn)圖)
- 沒(méi)法實(shí)現(xiàn)漸進(jìn)式加載體驗(yàn)(如先顯示模糊圖再加載高清圖)
這些需求,loading="lazy"
全都做不到!
4. 只能用于 <img>
和 <iframe>
,場(chǎng)景非常局限
如果你想懶加載:
- React / Vue 的動(dòng)態(tài)子組件?
都不能用 loading="lazy"
,完全不適配,只能乖乖用 JS 自己監(jiān)聽(tīng)。
正確的方案:IntersectionObserver
當(dāng)瀏覽器原生的 loading="lazy"
不能滿(mǎn)足我們?cè)?nbsp;加載控制、動(dòng)畫(huà)處理、骨架屏、兼容性、多元素懶加載 等方面的需求時(shí),更強(qiáng)大、更靈活、更現(xiàn)代的方式就是: IntersectionObserver
。

IntersectionObserver
是瀏覽器提供的一個(gè) API,它允許你觀察某個(gè) DOM 元素是否進(jìn)入或離開(kāi)視口(或某個(gè)指定容器),從而可以在適當(dāng)時(shí)機(jī)觸發(fā)事件,比如:
它會(huì)持續(xù)監(jiān)聽(tīng)一個(gè)目標(biāo)元素是否“交叉”進(jìn)入指定容器(通常是視口),并觸發(fā)回調(diào):
const observer = new IntersectionObserver(callback, options)
observer.observe(targetElement)
以 Vue
為例,我們可以通過(guò)如下方式來(lái)直接完成 懶加載指令
// vue3 指令實(shí)現(xiàn)(使用 @vueuse/core 的 useIntersectionObserver)
app.directive('lazy', {
mounted(el, binding) {
const { stop } = useIntersectionObserver(
el,
([{ isIntersecting }]) => {
if (isIntersecting) {
el.src = binding.value
stop()
}
},
)
},
})
<img v-lazy="imageUrl" alt="圖片">
這種方式雖然比 loading="lazy"
要復(fù)雜,但是在實(shí)際企業(yè)開(kāi)發(fā)中,這才是最可控的方式。
一句話總結(jié)下:如果是非常簡(jiǎn)單的頁(yè)面,那么使用 loading="lazy"
確實(shí)是最快捷的方式;但如果你需要更多控制、更復(fù)雜的體驗(yàn),IntersectionObserver
才是更強(qiáng)大的選擇!
?
閱讀原文:https://mp.weixin.qq.com/s/hc_qEthtOO_5OZrLuUgm8Q
該文章在 2025/9/16 12:26:21 編輯過(guò)