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

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











