阅读器维护一份独立于视觉渲染的虚拟内容缓冲区
别名: 虚拟缓冲区 · off-screen model · 屏幕外模型
概念解释
屏幕阅读器并不对着像素朗读。它先从无障碍接口拷出一份自己的副本,再让虚拟光标在这份副本上走动、发声。这份副本叫虚拟内容缓冲区(virtual buffer,也叫 off-screen model)。眼睛看见的是排版引擎画出来的盒子;耳朵听见的是缓冲区里的节点串。两条管道从一开始就不是同一份数据。
常被说成「阅读器在读屏幕」。那是光学字符识别的退路,不是默认路径。默认路径上,没进无障碍树的东西对阅读器等于不存在,哪怕它在画布上清晰可见。
机制
浏览器或操作系统把界面暴露成无障碍树,阅读器再把树序列化进缓冲区。绘制走合成器:层、变换、裁剪、透明度。序列化走另一条:角色、名称、文本节点、包含关系。两条管道没有「把这个像素的颜色念出来」的桥。所以 display: none 和 aria-hidden 会让节点从缓冲区消失,而「移到视口外」或「透明度为零」往往还留着——视觉藏住了,副本没删。
第二层是快照,不是直播。光标走的是已经拷好的文档,不是每一帧的帧缓冲。阅读器因此能提供文档式的浏览(按元素走、按字走),代价是它看见的永远是「拷贝当时的树」,而不是「此刻屏幕上正在闪的那一下」。
怎么研究
对照三份东西,而不是听一遍就算过:无障碍检查器里的树、阅读器自带的缓冲区查看(NVDA 的元素列表、JAWS 的虚拟查看器)、遮住显示器后方向键走出来的语音。Windows 上用 NVDA+Firefox 或 JAWS+Chrome 看 Web;原生应用改看平台检查器,不要假定一定有网页那种缓冲区。
自变量:节点是否进入无障碍树(display:none / 视觉隐藏 / aria-hidden / 纯画布绘制)、平台(Windows 网页阅读器 / VoiceOver / TalkBack)。
因变量:语音是否出现该节点、缓冲区列表是否含该节点、检查器树与语音是否同构。
自动化能抓「有没有属性」;抓不到「阅读器到底拷了哪一版树」。
边界
经典虚拟缓冲区是 Windows 网页阅读器(JAWS、NVDA)的架构;VoiceOver、TalkBack 更接近直接消费无障碍 API,缓冲形态不同,不能把 Insert 键刷新那一套原样搬过去。原生桌面或移动控件常常没有「整页 HTML 缓冲区」,只有控件树。用户主动打开 OCR / 图像识别时,阅读器才真正读像素——那是缓冲区空了之后的补救,不能拿来证明默认路径在读屏幕。游戏、地图、WebGL 若只把画面交给 GPU,缓冲区里就是一片空白。
怎么落地
- 任何要被听见的控件,必须在无障碍树上有对应节点,不能只画在 Canvas 或纯 CSS 背景里。
- 视觉隐藏用「仍在树上、只是移出或裁掉」的手法;不要用
display: none或aria-hidden藏起还需要被读的内容。 - 装饰层反过来:确定不该进语音的,从树上拿掉,而不是只做成看不清。
- 验证:打开无障碍检查器对照语音;再用 NVDA 或 JAWS 打开缓冲区/元素列表,遮屏走一遍。检查器有、语音无,或画面有、两份列表都无,就是副本和绘制已经分家。