图片懒加载怎么实现?
2026/8/3 7:33:15 网站建设 项目流程

图片懒加载本质上就是:

图片进入可视区域时再去加载,而不是页面一开始就把所有图片都请求下来。

这样做的好处:

  • 减少首屏请求数
  • 提升页面加载速度
  • 节省带宽
  • 提升用户体验

一、常见实现思路

一般有两种主流方案:

  1. 监听滚动 + 计算元素是否进入视口
  2. 使用IntersectionObserver

其中现在更推荐:

IntersectionObserver

因为性能更好,写法也更简单。


二、方案一:滚动监听实现

HTML

先不要直接把真实图片地址放到src,而是放到data-src

<img class="lazy" data-src="https://example.com/1.jpg" src="placeholder.jpg" /> <img class="lazy" data-src="https://example.com/2.jpg" src="placeholder.jpg" /> <img class="lazy" data-src="https://example.com/3.jpg" src="placeholder.jpg" />

这里:

  • src="placeholder.jpg":占位图
  • data-src:真实图片地址

JS 实现

function lazyLoad() { const images = document.querySelectorAll('img.lazy') images.forEach(img => { const rect = img.getBoundingClientRect() if (rect.top < window.innerHeight && rect.bottom > 0) { const realSrc = img.dataset.src if (realSrc) { img.src = realSrc img.classList.remove('lazy') img.removeAttribute('data-src') } } }) } window.addEventListener('scroll', lazyLoad) window.addEventListener('resize', lazyLoad) window.addEventListener('DOMContentLoaded', lazyLoad)

原理

getBoundingClientRect()

获取元素相对视口的位置。

比如:

const rect = img.getBoundingClientRect()

如果:

rect.top < window.innerHeight

说明图片顶部已经进入视口底部以上了,也就是快进入屏幕了。


可以再加节流优化

因为scroll触发很频繁,所以一般要配合节流

function throttle(fn, delay) { let timer = null return function () { if (timer) return timer = setTimeout(() => { fn() timer = null }, delay) } } const betterLazyLoad = throttle(lazyLoad, 200) window.addEventListener('scroll', betterLazyLoad) window.addEventListener('resize', betterLazyLoad) window.addEventListener('DOMContentLoaded', lazyLoad)

三、方案二:IntersectionObserver 实现

这是现在更推荐的方式。

HTML

<img class="lazy" data-src="https://example.com/1.jpg" src="placeholder.jpg" /> <img class="lazy" data-src="https://example.com/2.jpg" src="placeholder.jpg" /> <img class="lazy" data-src="https://example.com/3.jpg" src="placeholder.jpg" />

JS

const images = document.querySelectorAll('img.lazy') const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target const realSrc = img.dataset.src if (realSrc) { img.src = realSrc img.removeAttribute('data-src') img.classList.remove('lazy') } observer.unobserve(img) } }) }) images.forEach(img => observer.observe(img))

原理

IntersectionObserver会帮你监听:

  • 某个元素是否进入可视区域
  • 进入了多少
  • 什么时候离开视口

当图片进入视口时:

entry.isIntersecting === true

就把:

img.src = img.dataset.src

这样浏览器才会真正发起图片请求。


优点

相比滚动监听,它有这些优势:

  • 不需要频繁手动计算
  • 不需要一直监听scroll
  • 性能更好
  • 代码更简洁

四、支持提前加载

有时候不希望图片完全进入屏幕才加载,而是:

快进入屏幕时就提前加载

可以用rootMargin

const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target img.src = img.dataset.src observer.unobserve(img) } }) }, { root: null, rootMargin: '100px', threshold: 0 })

这里表示:

  • 距离视口还有100px时就开始加载

这样用户滚到图片位置时,图片往往已经加载好了。


五、封装一个通用懒加载函数

原生 JS 版

function initLazyImages() { const images = document.querySelectorAll('img[data-src]') if (!('IntersectionObserver' in window)) { images.forEach(img => { img.src = img.dataset.src img.removeAttribute('data-src') }) return } const observer = new IntersectionObserver((entries, obs) => { entries.forEach(entry => { if (!entry.isIntersecting) return const img = entry.target const realSrc = img.dataset.src if (realSrc) { img.src = realSrc img.onload = () => { img.removeAttribute('data-src') } img.onerror = () => { console.log('图片加载失败') } } obs.unobserve(img) }) }, { rootMargin: '100px 0px' }) images.forEach(img => observer.observe(img)) }

调用:

document.addEventListener('DOMContentLoaded', initLazyImages)

六、Vue 里怎么做

在 Vue 项目里,常见是封装成一个自定义指令


Vue3 自定义指令实现

export default { mounted(el, binding) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { el.src = binding.value observer.unobserve(el) } }) }, { rootMargin: '100px' }) observer.observe(el) } }

注册:

import { createApp } from 'vue' import App from './App.vue' import lazy from './directives/lazy' const app = createApp(App) app.directive('lazy', lazy) app.mount('#app')

使用:

<template> <img v-lazy="'https://example.com/test.jpg'" src="/placeholder.png" /> </template>

七、React 里怎么做

React 里一般封装成组件或 hook。

简单版:

import { useEffect, useRef, useState } from 'react' function LazyImage({ src, placeholder, alt }) { const imgRef = useRef(null) const [currentSrc, setCurrentSrc] = useState(placeholder) useEffect(() => { const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { setCurrentSrc(src) observer.unobserve(entry.target) } }) }) if (imgRef.current) { observer.observe(imgRef.current) } return () => observer.disconnect() }, [src]) return <img ref={imgRef} src={currentSrc} alt={alt} /> }

八、实际项目中常见细节


1. 占位图

懒加载时通常会先显示一张占位图:

  • 默认灰图
  • loading 图
  • 模糊缩略图

例如:

<img src="loading.png" data-src="real.jpg" />

等真实图加载完成后再替换。


2. 图片加载失败兜底

img.onerror = () => { img.src = 'error.png' }

避免图片裂开影响体验。


3. 骨架屏 / 渐进加载

真实项目里常配合:

  • 骨架屏
  • 模糊图占位
  • 渐显动画

比如图片加载完成后加个淡入:

img { opacity: 0; transition: opacity 0.3s; } img.loaded { opacity: 1; }
img.onload = () => { img.classList.add('loaded') }

4. 列表更新后的新图片

如果页面是动态渲染的,比如:

  • 分页加载
  • 无限滚动
  • 瀑布流

那么新插入的图片也需要重新observe


5. SSR 场景注意

如果是服务端渲染项目,要注意:

  • window不存在
  • IntersectionObserver只能在客户端执行

要放到挂载后执行。


九、为什么懒加载能优化性能

因为正常情况下,页面里如果有 100 张图片:

  • 浏览器一开始可能就会请求很多张
  • 首屏渲染变慢
  • 带宽占用变大
  • 图片很多时容易卡顿

而懒加载只会先加载首屏附近的图片,其他图片等滚动到时再加载,所以:

  • 首屏更快
  • 网络压力更小
  • 页面更流畅

十、面试回答怎么说

你可以这样答:

图片懒加载的核心思想是,当图片未进入可视区域时不去加载真实图片,只显示占位内容;当图片进入或接近视口时,再把真实地址赋值给img.src。实现方式早期通常是监听scroll事件,结合getBoundingClientRect()判断元素是否进入视口;现在更推荐使用IntersectionObserver,由浏览器帮我们监听元素和视口的相交情况,性能更好。实际项目中还会结合占位图、失败兜底、提前加载和动画优化体验。


十一、最推荐的现代写法

<img class="lazy" data-src="real.jpg" src="placeholder.jpg" />
const imgs = document.querySelectorAll('.lazy') const observer = new IntersectionObserver((entries, obs) => { entries.forEach(entry => { if (!entry.isIntersecting) return const img = entry.target img.src = img.dataset.src img.onload = () => img.classList.add('loaded') obs.unobserve(img) }) }, { rootMargin: '100px' }) imgs.forEach(img => observer.observe(img))

十二、一句话总结

图片懒加载就是先不加载真实图片,等图片进入可视区域时再设置src,推荐用IntersectionObserver实现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询