图片懒加载,真正难的不是加载

举报
柠檬-夏日清爽 发表于 2026/10/04 11:22:44 2026/10/04
【摘要】 懒加载的代码只有几行:loading="lazy"。但"让图片晚点加载"和"让用户看到图片时它刚好已经在加载"是两件事——后者才是难的地方。 一、原生方案够用的情况<img loading="lazy" decoding="async" alt="描述文字">loading="lazy" 是浏览器原生的,视口外的图片不加载。目前主流浏览器都支持,绝大多数场景用这一行就够了。两个配套属性:d...

懒加载的代码只有几行:loading="lazy"。但"让图片晚点加载"和"让用户看到图片时它刚好已经在加载"是两件事——后者才是难的地方。

一、原生方案够用的情况

<img  loading="lazy" decoding="async" alt="描述文字">

loading="lazy" 是浏览器原生的,视口外的图片不加载。目前主流浏览器都支持,绝大多数场景用这一行就够了。

两个配套属性:

  • decoding="async" —— 异步解码,不阻塞主线程。
  • width / height —— 写上具体数值,防止加载完成后的布局跳动。

第三个尤其重要:图片没加载时宽度未知,加载完成后突然撑开容器,页面会跳动。指定宽高让浏览器提前算出占位。

二、真正的问题:loading=“lazy” 有坑

坑一:只对图片有效,容器要撑住高度。

如果图片在一个高度自适应的容器里,加载完成前容器高度是 0,所有元素会挤在上面。解决办法是给容器一个 aspect-ratio:

.card-img {
  aspect-ratio: 4 / 3;      /* 提前占位 */
  object-fit: cover;
}

坑二:首屏图片不该用 lazy。

首屏图片如果用 loading="lazy",浏览器可能因为要先判断"在不在视口内"而延后加载,反而更慢。首屏用 loading="eager",甚至加 fetchpriority="high"。

坑三:Safari 早期版本不支持。 老 Safari 完全忽略这个属性,图片照常加载——等于没做懒加载,但也没有害处。

三、需要提前加载时:预判用户意图

懒加载的理想状态是:用户还没滚到那儿,图片已经开始加载了。 纯懒加载做不到这个,因为它以"进入视口"为触发点,那时才开始请求,已经晚了。

三个可以提前的手段:

一、IntersectionObserver 的 rootMargin。

const observer = new IntersectionObserver(
  (entries) => {
    for (const entry of entries) {
      if (!entry.isIntersecting) continue;
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);          // 加载完就不再观察
    }
  },
  {
    rootMargin: '300px',    // 视口下方 300px 就开始加载
    threshold: 0.01,
  }
);

document.querySelectorAll('img[data-src]').forEach((img) => observer.observe(img));

rootMargin: '300px' 是最实用的一个参数。用户在图片进入视口前 300px 时图片就开始下载了,滚动时几乎感觉不到加载过程。

不带 rootMargin 的 IntersectionObserver 和 loading="lazy" 效果差不多,加上之后才有"预加载"的意义。

二、鼠标悬停时预取。

card.addEventListener('pointerenter', () => {
  const img = card.querySelector('img[data-src]');
  if (img && !img.dataset.prefetched) {
    img.dataset.prefetched = '1';
    new Image().src = img.dataset.src;   // 提前发起请求
  }
}, { once: true });

用 new Image().src 而不是直接设 img.src,这样只下载不渲染,等到真正要用时几乎瞬间显示。

三、下一页的内容提前加载。

用户在第 1 页的时候就把第 2 页的缩略图拉过来:

// 滚动到接近底部时
function prefetchNextPage() {
  const next = currentPage + 1;
  if (prefetched.has(next)) return;
  prefetched.add(next);
  fetch(`/api/articles?page=${next}&limit=20`)
    .then((r) => r.json())
    .then((list) => {
      list.forEach((a) => { new Image().src = a.thumbnail; });
    });
}

四、骨架屏比灰图好

图片加载期间的占位方案,按体验排序:

1. 骨架屏(最好)

.card-img {
  background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 37%, #f0f0f0 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease infinite;
}

@keyframes shimmer {
  0%   { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

动画能掩盖"内容在动"这件事,用户感觉不到等待。纯灰色的块反而会让等待显得更明显。

2. 主色调占位(次之)

如果知道图片的大致色调(电商场景常见),直接用这个颜色占位,加载完成时颜色接近,视觉上几乎无缝。

3. 灰图 / 图标(最差)

用一个大点的模糊缩略图当占位效果不错,但要处理 blur 滤镜的兼容性和性能问题。

五、图片本身的优化

懒加载解决的是"不该加载的别加载",已经决定要加载的那张图,还可以优化。

一、按需选择尺寸。 服务端支持图片处理时(CDN 或者对象存储的图像处理),按显示尺寸请求:

<img " srcset="?w=200 200w, ?w=400 400w, ?w=800 800w"
     sizes="(max-width: 600px) 200px, (max-width: 1000px) 400px, 800px"
     loading="lazy" alt="描述">

srcset + sizes 让浏览器根据屏幕宽度和像素密度自动选合适的尺寸。手机和 PC 用同一张 1920px 的图是很浪费的。

二、响应式图片。 sizes 那一串是这个属性里最容易写错的部分,写错了会导致选出来的图过大。

三、现代格式。 WebP / AVIF 比 JPEG 小 30%~50%,主流浏览器都支持。需要兼容老浏览器时用 <picture>:

<picture>
  <source type="image/avif">
  <source  type="image/webp">
  <img loading="lazy" alt="描述">
</picture>

四、不要缩放已经在客户端的图。 传一张 4000px 的图然后用 CSS 缩到 400px,浪费的是用户的流量和时间。上传时存大图,渲染时按需请求小图。

六、错误和重试

懒加载的图片比普通图片更容易加载失败(因为加载时机不确定,可能网络状态已经变了):

img.addEventListener('error', () => {
  const retry = Number(img.dataset.retry ?? 0);
  if (retry >= 2) {
    img.src = FALLBACK_PLACEHOLDER;    // 用兜底图
    return;
  }
  img.dataset.retry = String(retry + 1);
  setTimeout(() => { img.src = img.dataset.src; }, 1000 * (retry + 1));
}, { once: true });

重试次数要有上限。 不限次数的话,一张本来就不存在的图片会一直重试下去。

七、判断有没有生效

Chrome DevTools 的 Network 面板里勾选 Img 筛选,只看图片请求,然后滚动页面:

  • 图片进入视口之前应该没有请求;
  • 滚动接近视口时开始请求;
  • 滚完之后所有图片都被加载。

如果一进页面所有图片都在请求,说明 loading="lazy" 没生效。 常见原因:图片在容器里但容器 display: none(隐藏元素里的图片不参与懒加载判断),或者用了 position: absolute 定位导致浏览器判断不了位置。

八、简单总结一下

懒加载的核心不是"怎么延迟加载",而是"什么时候开始加载"。

loading="lazy" 是最省事的选择,想要更好的体验就加 rootMargin 让加载提前一点。图片本身的格式和尺寸优化,往往比懒加载带来的提升更大。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。