直接用 opacity 过渡会导致闪动和页面重排,因骨架屏与真实内容需占据相同几何空间且不能触发 layout;应固定容器尺寸、用 visibility+opacity 占位、加 will-change 优化,并用 transition 配合延迟 visibility 切换。
骨架屏切换时如果只写 opacity: 0 → 1,真实内容一出现就“啪”一下弹出来,用户能明显感知闪动。更糟的是,当真实内容比骨架屏高或宽时,页面会重排(reflow),导致下方元素跳动——这不是动画问题,是布局塌陷。
关键在于:骨架屏和真实内容必须占据完全相同的几何空间,且过渡过程不能触发 layout。
height 和 min-height(不能依赖内容撑开)visibility: hidden + opacity: 0 占位,加载完成再切为 visibility: visible + opacity: 1
opacity,且必须加 will-change: opacity 提前提示合成层transition 更适合这种状态驱动的切换——骨架屏消失、内容出现是离散事件,不是时间轴控制。用 @keyframes 反而难对齐时机,还容易因重复触发导致动画卡顿。
推荐写法:
立即学习“前端免费学习笔记(深入)”;
.skeleton { transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);}.content { opacity: 0; visibility: hidden; transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s 0.3s;}.content.loaded { opacity: 1; visibility: visible;}
注意两点:visibility 的延迟过渡(0s 0.3s)确保它在 opacity 动画结束后才生效,避免闪回;cubic-bezier(0.4, 0, 0.2, 1) 比 ease 更顺滑,减少突兀感。
真实内容 DOM 渲染完成 ≠ 样式计算完成 ≠ 图片/字体加载完毕。过早切换会看到文字先出来、图片后加载导致高度变化。
requestAnimationFrame 延迟到下一帧,确保样式已应用img 的 load 事件,或用 document.fonts.ready 处理自定义字体waitForRender() 工具函数,内部组合 raf + offsetHeight 检查 + 可选资源监听示例片段:
function showContent(el) { el.classList.add('loaded'); requestAnimationFrame(() => { // 确保渲染完成后再允许交互 el.setAttribute('aria-hidden', 'false'); });}
服务端渲染时,骨架屏和真实内容不能同时存在 DOM 中,否则 hydration 会失败或产生不一致警告。常见错误是条件渲染写成 {loading ? <Skeleton /> : <Content />},导致客户端首次渲染没有骨架节点,CSS 过渡失效。
正确做法:
.skeleton { display: block; } / .skeleton.hidden { display: none; })opacity 和 visibility 控制可见性v-show 是 OK 的,但 v-if 会销毁节点,禁用过渡是否自然,往往取决于服务端和客户端是否共用同一套 DOM 结构——骨架屏不是“临时替身”,而是真实布局的一部分。