瀑布流首屏骨架必须服务端或构建时注入html,紧贴开头,尺寸与真实卡片完全一致,用aspect-ratio和固定宽高占位,背景位移动画替代opacity/transform,加载完成立即remove() dom。

瀑布流首屏骨架必须提前注入 DOM,不能等 JS 执行
骨架屏在瀑布流里失效,90% 是因为把它塞进了 Vue 或 React 的组件生命周期里——比如 onMounted 或 useEffect 里才渲染。此时白屏已发生,LCP(最大内容绘制)指标直接崩坏。
正确做法是服务端或构建时就把骨架结构写进 HTML,紧贴 开始处,且早于真实瀑布流容器:
- 骨架结构用纯
<div> 构建,不依赖任何框架逻辑 <li>每个骨架项宽高固定(如 <code>width: 300px),但高度用aspect-ratio: 4 / 3或padding-top: 75%模拟图片比例 - 禁止用
min-height或fit-content,否则骨架塌陷、后续真实卡片插入时重排抖动 - 移动端需加
@media (max-width: 768px)重设骨架宽度和列数,避免 Safari 把aspect-ratio当成无效声明而回退为 0×0 - 设
width: 320px(不能用百分比,防缩放抖动) - 外边距用
margin-bottom: 16px模拟gap,而非gap自身(骨架不用 Grid/Flex 布局,避免 JS 动态计算干扰) - 图片占位用
aspect-ratio: 16 / 9+overflow: hidden,文字行用line-height: 1.5× 行数设高,禁用height: 100% - 所有骨架元素加
overflow: hidden,否则渐变动画会从圆角边缘溢出 - CSS 写死:
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%) -
background-size: 200% 100%(Safari 15.4–16.6 不支持百分比?fallback 成200px 100%) - @keyframes 中写
0% { background-position: -200% 0; } 100% { background-position: 200% 0; },禁用0% → 100%写法,否则动画边界卡顿 - 动画时长设
1.8s,animation-timing-function: linear,避免ease-in-out停顿感 - 加兜底:
@media (prefers-reduced-motion: reduce) { animation: none; } - 查到骨架容器节点,调用
element.remove()(不是innerHTML = '',后者残留事件监听器) - 若骨架由 Web Component 渲染,确保
disconnectedCallback里清除IntersectionObserver实例 - 下拉刷新场景下,刷新前先判断是否已有骨架 DOM,有则先
remove(),再重新插入新骨架 —— 防止重复节点堆积 - SSR 页面 hydration 完成后,检查
document.readyState === 'complete',跳过客户端再次渲染骨架,避免闪一下
骨架项的尺寸与布局必须和真实瀑布流完全一致
瀑布流靠 JS 计算列高并定位,骨架若尺寸错位,用户会看到“先灰块后跳动卡片”。关键不是“看起来像”,而是“占位一模一样”。
例如真实瀑布流每列宽 320px、gap: 16px,骨架项也必须:
用 background-position 动画做流光,别碰 opacity 或 transform
瀑布流骨架常因动画掉帧显得卡顿,根源是用了 opacity 淡入或 transform: translateX() 平移——这些触发重绘,低端安卓机和旧版 Safari 尤其明显。
真正稳的方案只靠背景渐变位移:
加载完成时必须彻底移除骨架 DOM,不是隐藏
瀑布流页面反复滚动、下拉刷新时,如果只用 display: none 或 visibility: hidden 控制骨架显隐,动画仍在后台跑,内存泄漏+页签卡顿会随时间累积。
真实内容渲染完毕后,执行:
瀑布流骨架最难的不是写动画,而是让骨架尺寸、加载时机、DOM 生命周期三者严丝合缝。差一点,用户就看到白→灰→跳→稳,而不是灰→稳→稳。











