用 aspect-ratio 占位+object-fit 控制图片尺寸可防跳动,配合 IntersectionObserver 精准监听每张图、校验 naturalWidth 后再触发 opacity+transform 渐显动画,并加 will-change 提升性能。

直接用 IntersectionObserver + opacity 和 transform 组合就能实现,但关键不是“怎么加动画”,而是“怎么不让图片加载时跳动、闪屏、错位”。
为什么图片一加载就上跳或错行?
瀑布流里图片高度不固定,加载前若没预留正确高度,DOM 会突然撑开,导致后续所有图片重排——尤其在多列布局(column-count)或 CSS Grid 瀑布流中更明显。
- 错误做法:只设
min-height或空<div>占位 → 无法响应宽高比,加载后必然跳 - 正确做法:用
aspect-ratio+ 背景色占位,强制容器维持比例 - 必须配合
img { height: auto }的禁用(iOS Safari 会重排),改用object-fit: cover+ 显式宽高控制
渐显上浮动画的 CSS 写法要点
动画本身很简单,但触发时机和性能细节决定是否“顺滑”:
- 初始状态:
.waterfall-img { opacity: 0; transform: translateY(20px); } - 进入视口后加的类:
.loaded { opacity: 1; transform: translateY(0); transition: opacity 0.4s ease-out, transform 0.4s ease-out; } - 必须加
will-change: opacity, transform,否则低端 Android 或 iOS WebView 容易卡顿 - 避免用
visibility: hidden或display: none切换——它们会中断 layout 流程,导致瀑布流列计算异常
IntersectionObserver 的实际绑定陷阱
很多人写一遍 observer 就监听全部图片,结果一张图加载完,所有图都触发动画——因为没区分 entry.target。
立即学习“前端免费学习笔记(深入)”;
- 每个
<img>必须单独调用observer.observe(img),不能批量传入 NodeList 后统一处理 - 回调里一定要检查
img.complete && img.naturalWidth > 0,否则缓存图片会跳过load事件,直接走isIntersecting分支,但此时自然尺寸可能还没解析出来 -
rootMargin: '0px 0px 150px 0px'更稳妥(底部多预留 150px),防止快速滚动时漏掉刚进视口的图片 - 加载完成后立刻执行
observer.unobserve(img),否则重复进入视口会反复加类,可能触发多次 transition
最常被忽略的是:动画类(如 .loaded)必须在图片真实渲染完成后再添加——不是 load 事件触发时,而是 img.naturalWidth > 0 且容器已按 aspect-ratio 占位稳定之后。否则 translateY 动画会从错误起点开始,看起来像“抽搐上浮”。


















