AI 生成的网页为什么越滚越卡?从 IntersectionObserver 到图片懒加载优化

举报
yd_213270272 发表于 2026/11/12 00:06:30 2026/11/12
【摘要】 前言现在使用 AI 编程助手开发图片展示网站非常方便。例如,只需要提出需求:“帮我开发一个图片展示页面,支持卡片布局、滚动浏览和手机适配。”AI 很快就能生成一个看起来不错的网站。但是,当图片数量从十几张增加到几百张时,问题就可能出现:页面首次打开速度明显变慢。浏览器同时发出大量图片请求。用户还没看到的图片也在提前下载。页面滚动时偶尔出现卡顿。手机网络下流量消耗较大。这些问题不一定是 CSS...


前言

现在使用 AI 编程助手开发图片展示网站非常方便。

例如,只需要提出需求:

“帮我开发一个图片展示页面,支持卡片布局、滚动浏览和手机适配。”

AI 很快就能生成一个看起来不错的网站。

但是,当图片数量从十几张增加到几百张时,问题就可能出现:

  • 页面首次打开速度明显变慢。
  • 浏览器同时发出大量图片请求。
  • 用户还没看到的图片也在提前下载。
  • 页面滚动时偶尔出现卡顿。
  • 手机网络下流量消耗较大。

这些问题不一定是 CSS 写得不好,也可能是图片加载策略不合理。

本文从一个常见的图片列表场景出发,介绍如何使用浏览器原生懒加载能力和 IntersectionObserver,优化图片较多的网页。


一、为什么图片多了,网页就容易变慢?

假设一个网页展示 100 张图片,每张图片平均大小为 500 KB。

那么,仅图片资源的总大小就可能达到约 50 MB。

如果页面初始加载时便让所有图片开始请求,浏览器和网络连接都可能承受较大压力。

例如,下面这种写法:

<img src="images/photo-1.jpg" alt="风景图片 1">
<img src="images/photo-2.jpg" alt="风景图片 2">
<img src="images/photo-3.jpg" alt="风景图片 3">

对于数量较少的图片,这样写完全没问题。

但如果是一个很长的图片列表,用户最初可能只看到前几张,其他图片暂时并不需要。

因此,一个优化思路是:

优先加载用户当前需要看到的内容,其他图片等接近可视区域时再加载。

这就是图片懒加载的基本原理。

二、最简单的方法:loading="lazy"

现代浏览器原生支持图片懒加载。

只需要给图片增加一个属性:

<img
  src="images/photo-1.jpg"
  alt="山间风景"
  loading="lazy"
  width="600"
  height="400"
>

其中:

loading="lazy"

表示允许浏览器延迟加载暂时不需要的图片。

相比自己编写大量滚动监听代码,这种方式更加简单,也更容易维护。

不过,需要注意三个问题。

第一,懒加载不意味着图片必须进入屏幕以后才开始下载。

浏览器可能根据网络情况、图片位置等因素提前请求接近可视区域的资源。

第二,首屏关键图片不应该全部设置为 lazy。

例如网站顶部的大幅主视觉图片,如果延迟加载,反而可能影响用户看到主要内容的速度。

第三,应该尽量明确图片尺寸。

例如设置:

width="600"
height="400"

这样浏览器在图片实际下载完成前,就可以根据比例预留显示空间,减少布局移动。

三、什么时候需要 IntersectionObserver?

原生 loading="lazy" 已经能解决许多普通图片列表的需求。

但有些场景需要更灵活的控制。

例如:

  • 希望图片距离屏幕还有一段距离时就开始加载。
  • 需要在卡片进入可视区域时执行动画。
  • 需要统计某个元素是否进入视口。
  • 希望加载前展示自定义占位状态。

这时可以使用浏览器提供的 IntersectionObserver。

它能够观察某个元素与指定根区域的交叉情况。

与不断通过滚动事件检查每张图片的位置相比,IntersectionObserver 通常更适合实现这类可见性观察逻辑。

例如:

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      console.log("元素进入观察区域");
    }
  }
});

这里的:

entry.isIntersecting

用于判断观察目标是否与观察区域发生交叉。

不过,它不是精确的像素级可见性跟踪工具,也不保证回调在某个绝对时刻立即执行。

四、实战:使用 IntersectionObserver 实现图片懒加载

下面制作一个简单的图片展示页面。

功能包括:

  1. 展示图片卡片。
  2. 对屏幕外的图片延迟加载。
  3. 图片接近屏幕时提前开始下载。
  4. 加载成功后移除占位状态。
  5. 加载失败时显示提示。

项目结构:

lazy-image-demo/
├── index.html
├── style.css
├── script.js
└── images/
    ├── photo-1.jpg
    ├── photo-2.jpg
    └── photo-3.jpg

图片文件需要自行准备,建议使用经过压缩的图片,并确保拥有使用权限。

1. 编写 HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta
    name="viewport"
    content="width=device-width, initial-scale=1"
  >
  <title>图片懒加载演示</title>
  <link rel="stylesheet" href="style.css">
  <script src="script.js" defer></script>
</head>
<body>
  <main>
    <h1>图片懒加载演示</h1>
    <p>向下滚动,观察图片的加载过程。</p>

    <section class="gallery">
      <figure class="photo-card">
        <img
          class="lazy-image"
          data-src="images/photo-1.jpg"
          alt="风景图片一"
          width="600"
          height="400"
        >
        <figcaption>风景图片一</figcaption>
      </figure>

      <figure class="photo-card">
        <img
          class="lazy-image"
          data-src="images/photo-2.jpg"
          alt="风景图片二"
          width="600"
          height="400"
        >
        <figcaption>风景图片二</figcaption>
      </figure>

      <figure class="photo-card">
        <img
          class="lazy-image"
          data-src="images/photo-3.jpg"
          alt="风景图片三"
          width="600"
          height="400"
        >
        <figcaption>风景图片三</figcaption>
      </figure>
    </section>
  </main>
</body>
</html>

这里有一个关键设计:

我们没有立即给图片设置 src,而是把真实图片地址保存在:

data-src="images/photo-1.jpg"

这样,在 JavaScript 为图片赋予 src 之前,浏览器不会因为这个属性直接开始下载对应图片。

2. 编写 CSS

创建 style.css:

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 32px 16px;
  font-family: system-ui, sans-serif;
  background: #f8fafc;
  color: #0f172a;
}

main {
  max-width: 700px;
  margin: auto;
}

.gallery {
  display: grid;
  gap: 28px;
}

.photo-card {
  margin: 0;
  overflow: hidden;
  border-radius: 14px;
  background: white;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}

.lazy-image {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  background: #e2e8f0;
}

.photo-card figcaption {
  padding: 14px;
}

.lazy-image.is-error {
  background: #fee2e2;
}

这里使用 aspect-ratio 为图片预留空间。

可以减少图片加载前后卡片尺寸变化的问题。

需要注意,图片必须具有合适的宽高比例,否则可能出现裁剪或留白;是否裁剪取决于实际布局设置。

3. 编写 JavaScript

创建 script.js:

const images = document.querySelectorAll(".lazy-image");

function loadImage(img) {
  if (img.dataset.loading === "true") {
    return;
  }

  const src = img.dataset.src;

  if (!src) {
    return;
  }

  img.dataset.loading = "true";

  img.addEventListener(
    "load",
    () => {
      img.classList.add("is-loaded");
      img.removeAttribute("data-src");
    },
    { once: true }
  );

  img.addEventListener(
    "error",
    () => {
      img.classList.add("is-error");
      img.alt = `图片加载失败:${img.alt}`;
      img.removeAttribute("data-src");
    },
    { once: true }
  );

  img.src = src;
}

if ("IntersectionObserver" in window) {
  const observer = new IntersectionObserver(
    (entries) => {
      for (const entry of entries) {
        if (entry.isIntersecting) {
          loadImage(entry.target);
          observer.unobserve(entry.target);
        }
      }
    },
    {
      root: null,
      rootMargin: "200px 0px",
      threshold: 0
    }
  );

  images.forEach((img) => observer.observe(img));
} else {
  images.forEach(loadImage);
}

这个示例主要使用了三个关键技术。

首先,使用 IntersectionObserver 监听图片位置。

当图片与观察区域相交时,才调用加载函数。

其次,使用 rootMargin 提前加载。

rootMargin: "200px 0px"

表示将观察区域在垂直方向上向外扩展 200 像素。

因此,图片还没有真正进入可视区域时,也可能触发加载。

这样可以降低用户滚动到图片位置后才开始等待下载的概率。

最后,使用 unobserve() 停止不必要的观察。

图片开始加载后,不需要继续反复观察它的位置。

如果浏览器不支持 IntersectionObserver,代码会直接加载所有图片,保证基本功能可用。

需要说明:这个实现侧重懒加载机制,没有提供重新尝试加载失败图片的功能。如果面向正式产品,还可以增加重试按钮及错误占位图。

五、为什么不建议直接使用 scroll 事件实现?

有些 AI 生成的代码会使用:

window.addEventListener("scroll", () => {
  // 检查所有图片的位置
});

然后在每次滚动时,遍历所有图片并调用:

getBoundingClientRect()

来判断图片是否出现在屏幕内。

这种方式虽然能够工作,但如果滚动事件处理过于频繁,或者在每次滚动时读取大量布局信息,就可能增加主线程工作量。

特别是在图片很多、DOM 结构复杂的页面中,需要谨慎处理。

如果确实需要使用滚动事件,通常应当考虑节流,并减少不必要的布局读取。

但对于单纯的图片可见性判断,IntersectionObserver 往往是更简洁的选择。

六、如何测试图片是否真的实现懒加载?

只看网页能否显示图片,并不能说明懒加载已经成功。

可以通过浏览器开发者工具验证。

第一步:打开 Network 面板。

在 Chrome 或其他 Chromium 浏览器中,按 F12,切换到 Network。

第二步:筛选图片请求。

选择 Img 类型,观察图片资源的请求情况。

第三步:刷新页面。

检查是否只有首屏或接近首屏的图片开始请求。

需要注意,由于我们设置了 200 像素的预加载范围,首屏之外的部分图片也可能提前加载,这是正常现象。

第四步:缓慢向下滚动。

观察新的图片请求是否在接近可视区域时开始出现。

第五步:测试加载失败。

故意把一张图片路径写错,观察页面是否出现错误状态。

通过这些测试,可以判断代码是否真正按照预期执行,而不是仅仅在 HTML 中写了一个 lazy 字样。

七、懒加载是不是图片优化的全部?

当然不是。

图片网页的性能还受到很多因素影响。

优化方向 主要解决的问题
图片压缩 降低下载体积
WebP / AVIF 在适合的场景下改善图片压缩效率
srcset / sizes 为不同屏幕提供合适尺寸
loading="lazy" 延迟加载暂时不需要的图片
IntersectionObserver 自定义可见性触发逻辑
指定图片尺寸 减少布局偏移
CDN 缩短资源传输路径、改善分发能力

实际项目中,往往需要组合使用多种优化方法。

例如,一张 8 MB 的原始图片,即使通过懒加载延迟了请求,在真正显示时仍然需要下载大量数据。

因此,懒加载不能替代图片压缩。

另一个值得注意的问题是:

图片懒加载并不能解决超长页面中大量 DOM 节点造成的性能压力。

如果页面包含成千上万个复杂卡片,可能还需要考虑列表虚拟化或分页加载。

八、AI 编程助手开发图片网站时,应该增加哪些要求?

如果只告诉 AI:

“开发一个图片展示网站,要求加载流畅。”

它可能只关注页面样式和动画效果。

为了获得更可靠的结果,可以明确提出:

图片列表必须考虑首屏性能。非首屏图片优先使用浏览器原生懒加载;需要自定义提前加载距离时可使用 IntersectionObserver。明确图片宽高或宽高比,避免布局跳动。不要在每次 scroll 事件中遍历所有图片并反复强制计算布局。必须通过浏览器 Network 面板验证实际请求时机。

如果项目还要求支持手机访问,可以进一步要求:

根据设备屏幕大小使用合适分辨率的图片,避免手机端直接下载超大尺寸原图。

这样的描述比“优化性能”更容易执行和验证。

九、总结

AI 编程助手能够快速生成图片展示网页,但页面能够正常显示,不代表加载策略已经合理。

当图片数量增加时,浏览器可能面临更大的网络、内存和渲染压力。

通过本文的实践,可以总结出几个重要原则:

  1. 首先考虑浏览器原生 loading="lazy"。
  2. 需要自定义加载时机时,再使用 IntersectionObserver。
  3. 使用 rootMargin 可以提前加载即将进入视口的图片。
  4. 图片开始加载后,应停止不必要的观察。
  5. 为图片预留空间,减少页面布局变化。
  6. 使用 Network 面板验证实际加载行为。
  7. 图片压缩、响应式尺寸与懒加载应结合使用。

我认为,AI 生成网页时,真正需要关注的不只是页面能否显示出来,还应该考虑用户实际访问时的网络和设备条件。

一个体验良好的图片网站,不是让所有图片尽快开始下载,而是让用户需要的图片尽快出现。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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