图片懒加载时,如何让加载状态延迟显示更自然?
我在给图片列表加懒加载功能时遇到了问题,用loading="lazy"加上loading属性后,加载中的骨架屏总是在图片出现前0.5秒就消失了,看起来很突兀。我试过给骨架屏加固定延迟:
document.querySelectorAll('.image-item').forEach(item => {
item.querySelector('img').addEventListener('load', () => {
setTimeout(() => item.classList.remove('is-loading'), 800)
})
})
但发现网络好的时候图片加载更快,导致延迟时间反而更长。如果网速慢,骨架屏又会在图片出现前就消失。有没有更动态的办法让加载状态在图片真正准备好后再消失?
核心问题在于:图片真正加载完的那一刻,如果立刻移除骨架屏,用户会感觉“咔一下”跳出来,尤其是网络快的时候,确实很突兀。但你加死延迟又会反向伤害慢速网络体验——这说明你需要的不是时间上的延迟,而是视觉过渡的缓冲。
推荐两个更自然的做法,按推荐顺序来:
第一种,也是最稳妥的:用 CSS 的 transition 配合 opacity 做淡入淡出,而不是靠 JS 控制时间。骨架屏和真实图片重叠在一起,用一个 class 控制可见性切换,让浏览器自己处理过渡。这样无论网络快慢,用户看到的都是平滑的fade out -> fade in。
比如 HTML 结构改成这样:
CSS 里:
JS 只在图片 load 完后加个 is-loaded 类:
这样更清晰,而且你甚至可以微调 transition 的时间,让过渡更舒服。
第二种,如果必须用 JS 做延迟(比如你骨架屏里还有动画),那别用 setTimeout,改用 requestAnimationFrame 做“下一帧再消失”,配合一个最小延迟 + 最大延迟的组合判断:
这样网络快的时候,比如 100ms 就加载完了,也会至少再等 300ms 才消失;网络慢的时候,比如 800ms 才好,也不会等太久——它会“追着”加载时间走,但被限制在一个合理范围内。这样更自然,不会出现“图片出来了,骨架屏还在”的尴尬。
我一般更推荐第一种方案,因为可维护性高,而且不依赖 JS 的 timing,兼容性和性能都更稳。
IntersectionObserver来动态检测图片是否进入视口,然后再根据图片的实际加载情况来控制骨架屏的显示和隐藏。具体来说,先给每个图片元素设置一个默认的加载状态样式,比如骨架屏。然后通过
IntersectionObserver检测图片是否可见,当图片进入视口时,再绑定load事件监听器,确保图片完全加载完成后再移除骨架屏。代码可以这样写:
这个方法的好处是它不会固定延迟时间,而是根据图片的实际加载状态动态调整。如果图片加载得快,骨架屏会迅速消失;如果加载慢,骨架屏会一直保留到图片加载完成。另外,记得给
.is-loading状态加一点 CSS 过渡效果,比如淡出动画,这样视觉体验会更自然。对了,如果你发现某些图片在缓存情况下瞬间加载完成,也可以在代码里加上
img.complete的判断,避免骨架屏闪一下就消失的情况。希望这个方案能帮到你!