aria-busy="true"必须在fetch调用前立即设置,而非视觉loading出现后;它仅作用于已挂载、具语义角色(如role="region")的直接动态内容容器,且须配对设为false,不可与aria-live混用。

aria-busy="true" 必须在 fetch 前立刻设置
它不是视觉 loading 出现后才设的装饰属性,而是给屏幕阅读器发“暂停播报”的硬性指令。设晚了,NVDA 或 VoiceOver 已经开始读旧内容或中间态(比如空 div、残留列表项),再设 aria-busy="true" 也挡不住已发出的语音。
常见错误是把设置时机绑在 fetch().then() 里,或者等 React 的 useState 触发重渲染后再操作 DOM —— 这时请求早发出去了,UI 可能已闪烁,语义已断裂。
- 正确做法:调用异步逻辑前,直接对容器执行
el.setAttribute('aria-busy', 'true') - React 场景下,用
ref拿到真实 DOM 节点操作,别依赖 state 更新时机 - 必须配
.finally()或useLayoutEffect确保无论成功失败都设回aria-busy="false",漏掉就会让该区域永久“失声”
只加在直接承载动态内容的容器上
aria-busy 不是全局开关,加错位置等于失效甚至反效果。比如加在 或页眉导航栏上,整个页面会被读屏器跳过;加在按钮上则完全无意义——它只对内容容器起作用。
典型适用场景:ECharts 图表父 <div>、搜索结果列表 <code><div id="search-results">、表格重排序区域。这些容器需有明确语义角色,如 <code>role="region" 或 role="application"。
- 容器必须已挂载且有稳定 ID 或 ref,不能是条件渲染中尚未创建的节点
- 值只能是字符串
"true"或"false",写成布尔值true或"pending"都不被识别 - 不要和
aria-live区域混用——二者语义冲突,多数读屏器会优先遵守aria-busy并静音,导致aria-live彻底失效
配合 aria-hidden 和视觉反馈才能真正生效
aria-busy="true" 本身不隐藏内容、不禁用交互、不触发任何 CSS 变化。它只是告诉辅助技术“先别读”,但用户仍能看到空白、骨架屏或残留旧数据。所以必须手动补全视觉与交互层。
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
例如图表加载时,仅设 aria-busy="true" 不够,还要:aria-hidden="true" 隐藏旧图表、pointer-events: none 禁用点击、插入带文字的 loading 提示(如 <span aria-live="polite">加载中…</span>)。
- 骨架屏自身不能设
aria-live——它没文本,朗读为空白,反而干扰 - 按钮类加载态建议同时设
disabled和aria-disabled="true",兼顾原生禁用样式与语义 - 加载完成替换内容后,若需自动播报(如“找到 12 条结果”),应在设
aria-busy="false"后,再向容器内插入一个临时<span aria-live="polite"></span>元素
为什么设了 aria-busy 还被读到中间状态
根本原因不是属性没生效,而是 DOM 更新节奏和读屏器缓存机制不匹配。读屏器会缓存当前区域快照,aria-busy="true" 只阻止后续变更播报,不撤销已入队的语音。
比如你先清空容器、再插入“加载中…”、最后设 aria-busy="true",这中间几十毫秒的空窗,足够 NVDA 把空 div 或半截列表读出来。
- 解决方案:加载开始时就隐藏旧内容(
aria-hidden="true"),而不是等清空后再操作 - 避免用
setTimeout模拟等待——延迟不可控,且与真实渲染脱节 - 更可靠的做法:用
MutationObserver监听目标节点变化,或在框架生命周期钩子(如 React 的useLayoutEffect、Vue 的nextTick)中确认 DOM 已更新完毕再设aria-busy="false"
aria-busy="false",会让整个区域后续永远无法被屏幕阅读器感知。










