GPU无障碍
<canvas> 无论 2D 还是 GL,对读屏都是 一张位图。没有对象树,没有可聚焦的 mesh。可交互 GL(查看器、散点、地图)必须在 DOM 里另建投影:列表、滑块、live region。role="img" 只够静态图。
1. 静态 vs 可交互
<canvas role="img" aria-label="2026 Q3 销售额三维柱状图,华东最高"></canvas>
适用:海报、烘焙好的预览、不会被点的背景。
不适用:要选中标记、改相机、改筛选的产品——读屏只能听到一句标签,操作全丢。
可交互最低交付:
| 能力 | DOM 投影 |
|---|---|
| 对象列表 | listbox / 表格,与场景 id 双向同步 |
| 当前选中 | aria-activedescendant 或焦点移到对应行 |
| 相机 / 时间 | input type="range",改的是 CPU 相机,GL 只提交 |
| 动态结果 | aria-live="polite" 摘要,不要每帧报 |
场景图在 CPU,这件事反而简单:列表渲染的就是那份数组,不是 readPixels。
2. 键盘
轨道球和缩放必须有键盘等价:方向键平移、+/- 缩放、0 重置。焦点在 canvas 上时 tabindex="0",并在 keydown 里 preventDefault 掉会滚页面的键。焦点在 overlay 输入里时不要抢键。
命中高亮和列表高亮必须同一 selectedId。只在 GL 里画轮廓、列表不更新,等于视觉用户和读屏用户看到两套程序。
3. 不要拿 fallback HTML 当树
canvas 元素里的子 HTML 是 无脚本时的后备内容,现代浏览器有 GL 时读屏不一定进这棵树。生产把可访问控件放在 canvas 外面,不要塞进标签里当「隐藏 a11y」。细节同类问题见 Canvas 无障碍。
对比 SVG:节点可以挂 title / role。选 GL 就是放弃这棵树,必须用 overlay 买回来。
4. 失败形态
| 症状 | 原因 |
|---|---|
| 读屏只有「画布」 | 可交互图只用了 role=img |
| 键盘转了相机,列表还停在旧选中 | 两套状态 |
| 后备 HTML 有列表,读屏读不到 | 后备不在无障碍树上 |
| 每秒朗读一百次 | live region 绑在了 rAF |
权威资料
核对日期:2026-08-26