骨架屏必须用语义化空元素独立构建,禁用真实内容容器;css用渐变背景位移模拟流光动画;加载完成须彻底卸载dom并清除动画;ssr时骨架仅客户端注入,避免可访问性与水合错误。

骨架屏结构必须用语义化空元素,不能依赖真实内容容器
骨架屏不是把真实 DOM 节点透明化或加遮罩,而是独立构建一套轻量占位结构。如果复用 <div class="title"> 这类已有内容容器,后续 JS 动态替换时容易错乱、样式污染或触发多余重排。
<p>正确做法是用无语义的 <code><span></span> 或 <div> 配合专用类名,例如:
<pre class="brush:php;toolbar:false;"><div class="skeleton-wrapper">
<div class="skeleton-line skeleton-title"></div>
<div class="skeleton-line skeleton-paragraph"></div>
<div class="skeleton-avatar"></div>
</div></pre>
<ul>
<li>所有骨架元素不带文本、不设 <code>aria-label,避免被读屏器误读
<img> 或 <svg></svg> —— 它们会触发资源加载或渲染阻塞display,而非 JS 切换结构CSS 动画要用 background-image + linear-gradient 模拟流光
直接对 opacity 或 transform 做循环动画,视觉上只是闪烁或平移,缺乏“加载中”的质感。主流骨架屏效果本质是模拟光线扫过,核心靠渐变背景位移。
关键 CSS 片段:
.skeleton-line {
background-color: #f0f0f0;
background-image: linear-gradient(90deg, transparent, #e0e0e0, transparent);
background-size: 200% 100%;
animation: shine 2s infinite;
}
@keyframes shine {
from { background-position: -200% 0; }
to { background-position: 200% 0; }
}
- 不用
filter: blur()模拟灰度 —— 在部分 iOS Safari 上有性能抖动 - 动画时长建议 1.5–2.5s,太快显得焦躁,太慢失去反馈感
- 如果页面有深色模式,骨架色值要随主题切换,推荐用
CSS custom property如--skeleton-bg: #f0f0f0
加载完成时移除骨架必须同步销毁动画并释放内存
常见错误是只用 display: none 或 visibility: hidden 隐藏骨架节点,但 @keyframes 动画仍在后台运行,长期驻留影响页签性能,尤其在单页应用中反复进入/退出同一页面时。
- 务必用
element.remove()或element.innerHTML = ''彻底卸载 DOM 节点 - 如果骨架是通过 Web Component 或框架组件渲染的,确保
connectedCallback/unmounted钩子中清除定时器、事件监听和动画帧请求 - 移除时机要严格绑定数据加载完成事件,而不是 DOM 渲染完成 —— 否则可能闪现空白或内容错位
服务端渲染(SSR)场景下骨架屏不能出现在 HTML 源码中
服务端直出骨架屏看似能提升首屏感知速度,但实际破坏了语义和可访问性:搜索引擎爬虫和屏幕阅读器会把骨架当作真实内容索引或朗读,且无法区分“暂无数据”和“正在加载”。
- 骨架屏必须由客户端 JS 控制注入,初始 HTML 中只保留容器占位(如空
<div id="app"></div>) - 若使用 Next.js / Nuxt 等框架,骨架逻辑应放在
useEffect或onMounted中,避开 SSR 执行上下文 - 特别注意
hydration阶段:服务端无骨架、客户端插入骨架再替换,要防止 React/Vue 报 “Hydration mismatch” 警告 —— 可用suppressHydrationWarning或延迟骨架渲染至useLayoutEffect
真正难的不是画几条灰色横线,而是让骨架在各种加载路径、主题切换、SSR 和无障碍场景下都保持“看不见却刚刚好”。多数问题出在过早注入、过晚清理,或者把骨架当成样式问题来解,而它本质是个生命周期协调问题。











