Canvas 必须用 role="img" 容器包裹并同步更新 aria-label 或 aria-labelledby 所指文本,动态内容需配合 aria-live="polite" 和 aria-atomic="true" 确保屏幕阅读器实时、完整播报;aria-hidden="true" 会彻底阻断语义,fallback 文本不能替代实时可访问性描述。

Canvas 本身无法被屏幕阅读器理解,必须用明确的语义容器包裹并提供实时、同步的文本描述,否则无障碍用户完全“看不见”你画了什么。
为什么 aria-hidden="true" 是最常见也最危险的错误
很多开发者给 直接加 aria-hidden="true",以为“只是画图,不用读”。但问题在于:
- 它切断了所有语义通道——即使你后面加了 ,屏幕阅读器也不会把两者关联起来
- 当内容动态更新(比如图表数据刷新),
aria-hidden状态不会自动解除,导致新内容彻底失声- NVDA + Firefox 组合会缓存该状态,切换后真实内容仍不朗读
正确做法是:让
始终在 DOM 中,不加aria-hidden,改用外层容器承载描述逻辑。必须用
role="img"容器 + 同步aria-label或aria-labelledbyCanvas 不是图像元素,但你需要把它“声明为图像”,并绑定可读文本。关键不是“有没有描述”,而是“描述是否与当前视觉状态严格一致”:
- 静态图表:直接用
aria-label="2026年Q2销售额柱状图:A部门120万,B部门98万,C部门145万" - 动态图表:每次重绘后,必须同步更新
aria-label或所引用的文本内容 - 避免用
aria-describedby指向隐藏的,因为部分读屏器对动态更新支持不稳定;aria-labelledby更可靠示例结构:
<div role="img" aria-labelledby="chart-title chart-desc"> <h3 id="chart-title">实时温度趋势</h3> <canvas id="temp-canvas" width="600" height="300"></canvas><span id="chart-desc" aria-live="polite" aria-atomic="true">当前最高温32.4°C,最低温21.1°C,平均26.7°C</span> </div>
动态内容必须配
aria-live="polite"+aria-atomic="true"图表数值变化、悬停高亮、缩放等交互,不能只靠视觉反馈。屏幕阅读器需要被“主动通知”:
-
aria-live="polite"确保不打断当前朗读,但会在空闲时播报 -
aria-atomic="true"强制整段描述作为原子单位播报,避免只读出变化的数字而丢失上下文(比如只报“32.4”,不报“最高温”) - 不要用
aria-busy="true"标记加载中状态——它不触发朗读,仅作状态标记;真正要读的内容必须写进aria-live区域
注意:
aria-live区域必须是普通 HTML 元素(如),不能是自身,且不能设display: none或visibility: hidden(这会让读屏器跳过)。fallback 内容 ≠ 可访问性补救,它只兜底 JS 失效或 Canvas 不可用
很多人误以为在
标签里写点文字就是可访问了,比如:<canvas width="500" height="300"> 图表加载失败,请启用 JavaScript </canvas>
这是错的。这段文字只在 Canvas 渲染失败时显示,对正常运行的 Canvas 完全无效。真正的可访问性描述必须:
- 始终存在(不依赖 JS 是否执行成功)
- 与 Canvas 当前绘制内容语义一致(不是固定文案)
- 通过 ARIA 属性显式关联,而非 DOM 位置或隐含关系
最易被忽略的一点:如果你用 Canvas 实现的是一个交互式 HUD(比如游戏血条+技能冷却),那每个状态变化都得有对应描述——不是“HUD 已加载”,而是“血量剩余 63%,火焰冲击冷却中,剩余 2.4 秒”。描述粒度必须匹配视觉粒度。
-
- 当内容动态更新(比如图表数据刷新),
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











