aria-live="polite"容器必须在html初始加载时存在,动态添加无效;需写死在html中并用绝对定位隐藏,通知内容须用textcontent更新且防抖300ms,避免重复或语义干扰。

aria-live="polite" 容器必须在 HTML 初始加载时就存在
如果等 JS 动态创建再加 aria-live="polite",NVDA、JAWS、VoiceOver 基本都不读——它们只在元素挂载 DOM 的瞬间解析该属性。你不能靠 element.setAttribute('aria-live', 'polite') 补救。
正确做法是把容器写死在 HTML 里,比如放在 开头:
<div id="live-region" aria-live="polite" aria-atomic="true" class="sr-only"></div>
.sr-only 必须用绝对定位+尺寸归零的 CSS 隐藏(不能用 display: none 或 visibility: hidden),否则屏幕阅读器直接跳过。
普通通知内容更新必须用 textContent,别碰 innerHTML
通知文案要纯文本,不能含 HTML 标签。用 innerHTML 插入带样式的文字或图标,多数读屏会静默或读错(比如把 <span class="icon">✓</span> 读成“span icon”)。
更新方式只认两种:最稳的是 textContent 赋值;次选是 appendChild() 添加带文本的节点。例如:
const region = document.getElementById('live-region');<br>region.textContent = '订单已提交,预计2小时内发货';
- 每次推送前先清空:
region.textContent = '',避免旧内容残留干扰 - 防抖 300ms 再写入,尤其用于输入框实时校验类通知
- 跳过语义重复的更新,比如连续两次设为“密码强度:中”,第二次不触发
为什么写了 aria-live="polite" 却没播报?重点查这三处
polite 模式本身就会被跳过:用户正听导航栏、快速切换焦点、滚动页面时,它可能压根不出声。这不是 bug,是设计行为。所以别指望它“一定被听到”。
真没读出来,优先检查:
- 容器父级是否意外带了
aria-hidden="true"—— 会彻底屏蔽子级 live 区域 - DevTools 的 AX Tree 里,该元素是否显示
live: polite,且name字段有实际文本 - 是否用了
opacity: 0或transform: scale(0)隐藏容器 —— 视觉隐藏≠辅助技术可见
复杂点在于:同一段代码,在 NVDA + Chrome 和 VoiceOver + Safari 下,播报节奏和重读逻辑可能完全不同。测试时得覆盖至少两组主流组合,不能只信一种结果。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











