Canvas 默认对屏幕阅读器“隐身”是因为渲染后DOM中只剩空元素,无语义节点、文本流或焦点路径;必须用结构化备用内容(如带scope的table)配合role="img"和aria-describedby显式关联,并动态同步更新以满足WCAG要求。

Canvas 本身不可访问,必须靠备用内容 + ARIA 显式关联才能满足 WCAG 基础要求;单纯写个 <canvas>您的浏览器不支持 Canvas。</canvas> 远不够。
为什么 Canvas 默认对屏幕阅读器“隐身”
Canvas 是位图绘制表面,渲染后 DOM 中只剩一个空元素,没有语义节点、没有文本流、没有焦点路径。屏幕阅读器扫不到内容,也无法把“画出来的柱状图”自动转成可导航的结构化数据。
常见错误现象:
- 用
aria-label或title给<canvas>加描述,但只读出一句话,无法支持逐项浏览(比如“2023年销售额:Q1 120万,Q2 145万…”) - 把完整图表数据塞进
aria-describedby指向的<div>,结果该<div>被 CSS 隐藏或未设role="region",导致部分 AT 完全忽略 - 用
<figure><figcaption>包裹,但没给<canvas>加role="img",VoiceOver 等直接跳过整个区域
备用内容必须是结构化 HTML,不是纯文本
WCAG 1.3.1 要求信息关系不能仅靠视觉呈现。这意味着:柱状图不能只靠“Q1: 120万”这行字,而要还原成带行列语义的表格或定义列表。
立即学习“前端免费学习笔记(深入)”;
实操建议:
- 用
<table>替代内容时,<th scope="col">标明列头,<th scope="row">标明行组(如“产品A”“产品B”),避免仅靠colspan推理逻辑 - 多维图表(如带分组+时间轴+类别)拆成多个
<section>,每个含独立<h2>和<table>,用aria-labelledby关联标题 - 备用内容块必须和
<canvas>在同一<figure>内,或紧邻其后,确保 DOM 顺序与逻辑顺序一致 - 给
<canvas>加role="img",并用aria-describedby="id-of-table"显式绑定;不要依赖aria-label单点描述
动态图表的可访问性陷阱
Canvas 动画或实时更新图表时,仅刷新画布像素,DOM 不变 → 屏幕阅读器收不到变化通知,用户无法感知新数据。
关键处理点:
- 每次数据更新后,同步更新备用内容(如重写
<table>的<tbody>),而非只重绘 Canvas - 用
aria-live="polite"包裹备用内容区域(如<div aria-live="polite"><table>...</table></div>),让 AT 主动播报变更摘要 - 避免在 Canvas 上模拟按钮/滑块等控件;真需要交互,用原生 HTML 元素(
<button>,<input type="range">)叠加在 Canvas 上,并通过 JS 同步状态 - 若 Canvas 用于导出(如生成 PNG),确保导出前已将当前数据快照写入备用内容,否则“另存为图片”后语义彻底丢失
最易被忽略的是:备用内容不是“写一次就完事”的静态文案,它必须随 Canvas 渲染状态实时同步,且结构必须能被 AT 导航 —— 否则所谓“可访问”,只是对搜索引擎或禁用 JS 的用户有效,对真实残障用户毫无意义。



















