图片懒加载后为什么还是卡顿?

小倩 阅读 30

我在项目里用Intersection Observer做了图片懒加载,但页面滚动时还是卡顿,尝试过把换成标签并添加WebP格式,也调整了threshold到0.1,但效果不明显。代码结构是这样的:


const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
}, { threshold: 0.1 });

document.querySelectorAll('.lazy-img').forEach(img => observer.observe(img));

图片服务器已经做了响应式尺寸裁剪,但移动端加载大图时帧率还是掉到20多,有没有其他优化点没考虑到?

我来解答 赞 9 收藏
二维码
手机扫码查看
2 条解答
雅涵
雅涵 Lv1
你这个问题挺典型的,懒加载本身没问题,但卡顿的根因往往不在懒加载本身。

从后端角度说几个可能的优化点:

1. 图片尺寸和大小没真正控制住

服务端裁剪了尺寸,但文件大小不一定小。你需要确认移动端实际加载的图片大小控制在多少KB。移动端单张图最好别超过100KB,可以用 TinyPNG 或者 ImageMagick 压一下。服务端可以加个自动压缩逻辑,返回前统一用 webp 并且 quality 压到 75-80。

2. 没使用 srcset + sizes

光有响应式裁剪不够,你得让浏览器自己选。改成像这样的结构:


  class="lazy-img" 
data-src="https://cdn.example.com/image/large.jpg"
srcset="https://cdn.example.com/image/small.jpg 400w,
https://cdn.example.com/image/medium.jpg 800w,
https://cdn.example.com/image/large.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
alt=""
>


这样浏览器会根据视口宽度自己选合适的图,不会傻傻加载大图。

3. 缺少 GPU 加速

给图片容器加个 CSS 属性,触发 GPU 渲染:

.lazy-img {
will-change: transform;
transform: translateZ(0);
}


这个能明显减少滚动时的重绘开销。

4. placeholder 的问题

你如果直接等 src 赋值那一瞬间才显示,图片解码那一下是会卡顿的。建议先用一个低质量的 blur 图或者纯色占位,等大图加载完再替换。服务端可以生成一个 tiny 版本(比如宽 20px,base64 内联),先显示这个。

5. 检查缓存

看看服务端返回的图片有没有带 Cache-Control 和 ETag,没有缓存的话每次滚动都在网络层面也有开销。

核心问题基本就是这些。你先排查一下图片实际文件大小和 srcset 这两点,移动端帧率应该能回升到 50+。
点赞
2026-03-11 14:05
迷人的树恺
你这个情况大概率是图片解码导致的卡顿,尤其是大图在移动端设备上。拿去改改,试试这样处理:

先把图片解码放到Web Worker或者提前解码,不要让浏览器在主线程里硬扛。代码可以改成这样:

const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const src = img.dataset.src;

// 创建一个临时图片对象进行预加载
const tempImg = new Image();
tempImg.src = src;
tempImg.decode().then(() => {
// 解码完成后再赋值给真实img标签
img.src = src;
img.classList.add('loaded'); // 可以加个class控制样式
}).catch(() => {
console.log('图片加载失败', src);
});

observer.unobserve(img);
}
});
}, { threshold: 0.1, rootMargin: '50px 0px' }); // 提前加载

document.querySelectorAll('.lazy-img').forEach(img => observer.observe(img));


另外,别忘了把CSS里的 .lazy-img 默认设置成透明度0,等加载完再渐显,体验会好很多:

.lazy-img { opacity: 0; transition: opacity 0.3s ease-in-out; }
.lazy-img.loaded { opacity: 1; }


还有几个点你可以注意下:
- 图片太大了就分块加载,比如用 background-image 配合 CSS 的 background-position 懒加载部分内容
- 如果图片数量特别多,考虑用虚拟列表,只渲染可视区域内的内容
- 最后,检查下是不是有其他脚本或者样式影响了性能,比如复杂的选择器或者频繁触发的事件监听

我之前也踩过类似的坑,后来发现是图片太大加上解码耗时,搞到主线程阻塞了。按照这个思路优化一下,应该能解决大部分问题。
点赞 9
2026-02-18 18:04