直接用CSS opacity动画会导致瀑布流错位,因opacity不触发重排,而瀑布流依赖图片加载后真实高度撑开容器;骨架占位需固定高度(如padding-bottom按宽高比),图片加载完成且进入视口后再淡入。

为什么直接用 CSS opacity 动画会导致瀑布流错位
因为 opacity 不触发重排(reflow),但瀑布流布局依赖图片加载后真实高度撑开容器。如果图片还没加载完成,骨架占位高度又不匹配最终图片尺寸,opacity: 0 → 1 过渡时容器高度突变,会引发下方所有列内容跳动、错位甚至重叠。
真正要做的不是“淡入”,而是“等高度稳定后再淡入”。关键在控制动画触发时机,而非仅靠 CSS。
- 骨架占位必须有**固定或可预测的高度**(如按宽高比计算:
padding-bottom: 66.67%) - 图片加载完成(
onload)或naturalWidth > 0确认尺寸后,再移除骨架、添加fade-in类 - 避免对
img直接设opacity,改用包裹容器(如<figure></figure>)控制显隐,防止 img 自身渲染干扰布局流
如何用 IntersectionObserver + 图片加载状态做精准触发
单纯等 img.onload 不够:用户快速滚动时,图片可能尚未进入视口就已加载完成,导致提前动画、白屏或错位。应结合可视性判断,只对即将可见的图片启用骨架→图片过渡。
示例逻辑:
立即学习“前端免费学习笔记(深入)”;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
if (img.complete && img.naturalWidth > 0) {
img.parentElement.classList.add('loaded');
} else {
img.addEventListener('load', () => {
img.parentElement.classList.add('loaded');
});
}
observer.unobserve(img);
}
});
}, { rootMargin: '50px' });
document.querySelectorAll('.waterfall-img').forEach(img => observer.observe(img));
-
rootMargin: '50px'提前加载,避免滚动过快时空白 - 必须检查
img.complete,否则缓存图片会跳过load事件 - 只对进入视口的元素监听,减少无谓绑定
骨架占位的 HTML 结构与 CSS 关键约束
不能用空 <div> 加背景色模拟——它无法响应图片宽高比变化。必须让骨架本身具备流式比例能力。
<p>推荐结构:</p>
<pre class='brush:php;toolbar:false;'><figure class="skeleton-wrapper">
<div class="skeleton-placeholder"></div>
<img src="..." alt="" class="waterfall-img">
</figure>
</pre>
<p>CSS 核心点:</p>
<ul>
<li>
<code>.skeleton-wrapper 设为 position: relative,确保子元素定位上下文统一
.skeleton-placeholder 用 padding-bottom: calc(100% / var(--aspect-ratio)) 维持宽高比(需提前知道或服务端注入 --aspect-ratio: 1.5)img 设 position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;,覆盖骨架且不撑开容器.loaded img 加 opacity: 1; transition: opacity 0.3s ease;,同时 .loaded .skeleton-placeholder 设为 display: none
服务端未提供宽高比时的降级方案
很多 CMS 或图床不返回原始宽高,前端无法预知 --aspect-ratio。硬写死比例(如 4:3)会导致大量图片被裁切或留白。
可行路径只有两条:
- 用
img.decode()异步获取尺寸(兼容性 OK,但会阻塞动画触发,需加 timeout) - 加载前先发 HEAD 请求查 Content-Length + 缓存策略,配合懒加载库(如 lozad.js)内置的宽高推测逻辑
- 最简方案:CSS 中 fallback 到
aspect-ratio: 16/9(现代浏览器支持),并用@supports not (aspect-ratio: 1)回退 padding-bottom 方案
注意:不要在 JS 里读取 img.naturalWidth 后再动态设 padding-bottom——这会触发重排,瀑布流列高立刻错乱。


















