canvas元素默认对屏幕阅读器不可见,必须显式添加role="img"并配合aria-label或aria-labelledby提供文本描述,同时保留回退文本以实现双重保障。

canvas 元素默认对屏幕阅读器不可见
浏览器遇到 <canvas></canvas> 时,除非显式声明语义,否则直接跳过——它被当作“空画布”处理,不进可访问性树。哪怕你画满了图表、检测框、热力图,读屏器用户也听不到任何描述。这不是 bug,是规范行为:Canvas 是位图容器,本身无语义。
常见错误是只加 title 属性或靠 JS 插入提示文本:<canvas title="销售趋势图"></canvas>。但 title 只在悬停时显示,键盘用户和读屏器基本忽略;JS 动态插入的内容若未配合 aria-live,脚本禁用时又彻底消失。
- 必须用
role="img"显式告诉辅助技术:“这是一个图像类内容” - 必须配
aria-label或aria-labelledby提供文本描述,二者选其一即可 - 不能同时写
aria-label和label(<label for="..."></label>),否则可能重复播报
aria-label vs aria-labelledby:什么时候该用哪个
aria-label 适合静态、简短、无需复用的描述,比如按钮图标或单张图表。它直接写死在标签上,不依赖 DOM 中其他节点。
aria-labelledby 适合复用已有文本(如标题、说明段落),避免重复维护。但要求目标元素存在且 ID 唯一,否则读屏器会静默失败。
- 用
aria-label:<canvas role="img" aria-label="2024年Q2手机销量柱状图,共12个品牌数据"></canvas> - 用
aria-labelledby:<h3 id="chart-title">月度检测准确率对比</h3> <canvas role="img" aria-labelledby="chart-title"></canvas> - 别这样写:
<canvas role="img" aria-labelledby="missing-id"></canvas>——ID 找不到,描述就丢了
回退文本 + aria-label 必须共存,缺一不可
<canvas></canvas> 内部的回退文本(即开始与结束标签之间的内容)和 aria-label 不是替代关系,而是面向不同降级场景的双重保障:
- 回退文本用于脚本禁用、JS 加载失败、或极老浏览器场景,必须是纯 HTML 内容,不能靠 JS 注入
-
aria-label用于 JS 正常运行但读屏器需要语义的场景,仅影响可访问性树 - 两者都得写,且描述应一致或互补,例如回退文本写用途+数据范围,
aria-label写更精炼的用途
正确写法示例:
<canvas id="detection-canvas" width="600" height="400" role="img" aria-label="DAMO-YOLO 手机检测结果图"> 此处显示手机检测框与置信度数值。若需查看原始图片与详细报告,请下载结果文件。 </canvas>
Safari + VoiceOver 下 aria-label 的实际生效条件
在 Safari 中,aria-label 能否被 VoiceOver 读出,不仅取决于属性是否写对,还受渲染时机和焦点状态影响:
- Canvas 初始化后立即设置
aria-label,但若此时元素尚未挂载到可访问性树(例如父容器display: none或aria-hidden="true"),VoiceOver 会忽略 - 动态更新
aria-label(如检测完成后重设)不会自动触发播报,必须配合aria-live="polite"或手动.focus()(但后者在 Safari 中不保证触发) - 最稳妥做法:Canvas 首次渲染完成后再设置
role和aria-label,并确保其父容器没有aria-hidden="true"
容易被忽略的一点:Canvas 容器外层如果用了 display: contents,Chrome 会剥离其可访问性节点,Firefox 保留但重建逻辑不同——这种 CSS 技巧在无障碍上下文中风险极高,慎用。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











