骨架屏必须与真实dom结构完全对齐,复用语义标签、class、data属性,用line-height和aspect-ratio等css显式撑开占位空间,并在客户端加载后立即销毁带data-skeleton标识的节点。

骨架屏在 HTML 文档结构中不是“加个 div 就完事”,它必须和真实内容 DOM 完全对齐,否则加载完成时必然跳动、重排、闪抖——用户感知的“流畅”就断在这一帧。
骨架节点必须复用真实标签语义和 class 层级
常见错误是用一堆 <div class="skeleton"> 替代真实结构,比如真实卡片是 <code><article><header><h2></h2></header><figure><img></figure></article>,骨架却只写 <div class="skeleton-card"></div>。结果:CSS 选择器失效、Flex/Grid 子项行为错乱、line-height 计算失准、屏幕阅读器无法建立上下文。
正确做法是逐层复制语义结构:
- 真实内容用
<header></header>,骨架也用<header></header>;真实用<figure></figure>,骨架不能降级为<div> <li>所有 class 名(如 <code>post-title、card-image)必须完全一致,大小写、连字符、命名空间都不能差 - data 属性(如
data-id、data-type)若存在,骨架节点也得带上,否则 hydration 或 replaceWith() 时会被框架当作“不同节点”处理 - 文字块:用
line-height × 行数设定固定高度,例如三行摘要 →height: 3.6em(假设line-height: 1.2) - 图片:现代浏览器优先用
aspect-ratio: 16 / 9;兼容旧版则用padding-top: 56.25%+position: relative+ 子元素position: absolute - 所有骨架元素加
overflow: hidden,否则 shimmer 动画会溢出圆角边界 - 服务端吐出的骨架节点必须带明确标识,例如
data-skeleton="true"或aria-busy="true",方便客户端精准定位 - 真实内容插入后,必须调用
element.remove()销毁整个骨架节点,而非仅设display: none或visibility: hidden - 若用
replaceWith()替换,确保传入的是 DocumentFragment 或真实 DOM 节点,避免字符串拼接导致事件监听器丢失、表单值清空
图片与文字占位必须脱离内容依赖,靠 CSS 显式撑开空间
骨架塌陷 90% 的原因,是浏览器把空元素渲染成 0×0 ——width: 100% 或 height: 2em 在父容器无高时根本无效。文字行不能靠 height: 100%,图片不能靠 <img src="placeholder.png">。
必须用可预测、不随字体/设备浮动的尺寸控制方式:
服务端直出时,骨架节点要带可识别标记,且 JS 加载后立刻销毁
骨架不是“临时贴纸”,它是首屏 HTML 的一部分,但绝不能长期驻留。SSR 页面里骨架若没被及时清理,会导致动画持续运行、内存泄漏、IntersectionObserver 误判、甚至影响页签后台性能。
关键操作点:
最容易被忽略的点:骨架和真实内容的 DOM 树深度、注释位置、甚至空格缩进,都可能影响 Vue/React 的 hydration 一致性。只要一个层级错位,状态就断了——这不是样式问题,是结构契约问题。











