html骨架屏是需手写的真实dom结构,非placeholder属性;须严格对齐尺寸与层级,用opacity切换而非display:none,配合intersectionobserver管理无限滚动,并适配无障碍与低配设备。

HTML骨架屏不是占位符属性 placeholder
很多人一看到“占位”,就下意识去翻 input 的 placeholder 属性,但这是两回事。placeholder 是表单控件的提示文字,只在空值时显示、聚焦即消失,不撑布局、不参与渲染节奏,更不会模拟结构。骨架屏是独立的一套 DOM 结构(比如一堆 div 套 class="skeleton-title"),它必须和真实内容在层级、宽高、class 上严格对齐,否则替换时会重排、状态丢失、动画错位。
常见错误包括:
- 把
placeholder当成通用加载提示,在非表单元素上乱加 - 用
visibility: hidden包裹真实内容,却没给骨架元素设height和width,导致骨架一渲染就塌陷 - 骨架里用了
img标签但没设src,触发 404 请求或 layout shift
骨架屏必须自己写 HTML 结构,不能靠 CSS 生成
CSS 的 ::before/::after 或伪元素无法撑开容器高度,也不能被 IntersectionObserver 正确监听;骨架屏需要的是可测量、可替换、可复用的真实 DOM 节点。所以你得手写结构,哪怕只是几个 div:
<div class="article-skeleton">
<div class="skeleton-avatar"></div>
<div class="skeleton-content">
<div class="skeleton-title"></div>
<div class="skeleton-paragraph"></div>
<div class="skeleton-paragraph short"></div>
</div>
</div>
关键点:
- 每个骨架元素必须有明确尺寸:用
width/height,或现代方案用aspect-ratio - 避免用
display: none控制显隐——它会让元素脱离文档流,造成布局跳动 - 推荐用
opacity: 0+pointer-events: none配合 transition,切换更平滑
无限滚动中骨架屏的触发和销毁必须配对管理
无限滚动不是“滚到底才加载”,骨架屏也不是“一加载完就删”。如果每次请求都新建一套骨架 DOM,又没清理旧的,就会内存泄漏;如果直接 innerHTML = '' 清空容器,Vue/React 组件状态、focus、第三方插件(如代码高亮)全丢。
正确做法是:
- 用
IntersectionObserver监听一个固定占位符节点(如<div id="infinite-trigger"></div>),配合{ rootMargin: '200px' }提前触发 - 加载中,动态插入新骨架项(不是覆盖整个列表),保持与后续真实数据结构一致
- 数据返回后,用
replaceWith()替换对应骨架节点,而非清空再 append - 服务端必须返回
hasMore: true/false,前端据此决定是否继续 observe#infinite-trigger
低配设备和无障碍场景下骨架屏容易失效
动画太花哨、DOM 太深、没关掉动画偏好,都会让骨架屏变成负优化。尤其要注意:
- 开启
prefers-reduced-motion时,应禁用背景动画,改用静态灰块:@media (prefers-reduced-motion: reduce) { .skeleton-title { animation: none; background-color: #eee; } } - 骨架元素不能缺失语义:比如用
role="status"和aria-live="polite"告知屏幕阅读器“内容正在加载” - 避免用
will-change: transform过度优化,部分低端安卓 WebView 会因此卡顿或闪屏 - 骨架结构若含图片占位,务必用
loading="lazy"防止预加载干扰主资源
最常被忽略的是:骨架屏不是越像真实页面越好,而是越快渲染、越少重排、越易替换越好。结构一致性比视觉精致度重要得多。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











