推荐用 visibility + opacity 组合控制 loading 显隐,配合 pointer-events: none 和 aria-busy 实现无布局跳动、无障碍、防重复触发的局部加载状态。

用 display: none 控制 Loading 容器显隐不靠谱
直接切 display 会导致布局跳动、焦点丢失,尤其在表单或可交互区域里,用户操作可能中断。更糟的是,如果 Loading 容器本身有过渡动画,display 会强制清空所有 CSS 动画状态,导致闪烁或卡顿。
推荐做法是用 visibility + opacity 组合:
-
visibility: hidden保留文档流位置,避免重排 -
opacity: 0配合transition实现淡入淡出(比如transition: opacity 0.2s ease) - 加载中时设为
visibility: visible; opacity: 1,隐藏时反向设置 - 务必给容器加
pointer-events: none,防止遮挡底层元素的点击
用 aria-busy="true" 告诉屏幕阅读器当前不可交互
局部 Loading 不只是视觉反馈,还要兼顾可访问性。仅靠 CSS 隐藏/显示无法让读屏软件感知状态变化,用户可能误操作或困惑“为什么按钮点了没反应”。
aria-busy 是专为此设计的语义属性:
- 目标容器(如
<div class="card">)上添加 <code>aria-busy="true"表示正在处理 - 加载完成时移除该属性,或设为
aria-busy="false" - 配合
aria-live="polite"可选地播报状态(例如 “数据加载中,请稍候”),但需注意不要频繁触发干扰 - 不要用
aria-hidden替代aria-busy—— 前者会完全屏蔽内容,后者只表达“暂不可用” - 检查目标容器是否已有
aria-busy="true",有则直接 return - 用
AbortController关联请求与 Loading 状态,避免请求取消后状态滞留 - 统一用 Promise.finally() 清理状态,而不是只在
.then()和.catch()里分别写 - 示例片段:
function loadContent(container) { if (container.getAttribute('aria-busy') === 'true') return; container.setAttribute('aria-busy', 'true'); fetch('/api/data') .then(r => r.json()) .then(data => render(data)) .finally(() => container.setAttribute('aria-busy', 'false')); } - 旋转类用
transform: rotate()+will-change: transform提升到合成层 - 骨架屏用
linear-gradient模拟灰块,配合animation模拟流动感 - 避免在
@keyframes中修改width/height,这会触发重排;改用scale或translate - 给 Loading 元素加
flex-shrink: 0,防止父容器 flex 布局下被压缩变形
JavaScript 中控制 Loading 状态要防重复触发
用户快速连点按钮、网络请求未完成就再次提交,容易导致 Loading 状态错乱:比如 aria-busy 没及时恢复,或多个 Loading 层叠出现。
关键是在发起异步操作前加守卫逻辑:
CSS 加载指示器别用 GIF,优先用 @keyframes + transform
GIF 在高 DPI 屏幕上易模糊,且无法响应主题色或尺寸缩放;更重要的是,它会持续占用解码线程,滚动或动画时可能掉帧。
纯 CSS 实现更轻量、可控性强:
aria-busy 的同步时机和 pointer-events 的默认缺失——这两个点一漏,Loading 就从体验优化变成体验陷阱。











