Android TalkBack无法访问可滚动聚焦容器内内容的原因与解决方法
问题背景
我需要创建一个可通过键盘访问的滚动容器,该容器是设置了overflow: scroll的div,且已添加tabindex="0"使其可聚焦。桌面端NVDA屏幕阅读器可正常读取容器内内容,键盘滚动也能正常工作,但Android的TalkBack屏幕阅读器无法访问容器内的内容。
容器结构类似MagentaA11y的滚动容器示例,但内部是段落和列表项而非表格,代码如下:
<p>Some content outside container</p> <div role="region" aria-label="Container name" tabindex="0"> <p>Some content</p> <p>Some content</p> <ul> <li>Some content</li> <li>Some content</li> </ul> </div>
通过TalkBack可导航至该div,但无法访问其中的段落和列表项。测试发现,若在容器内添加交互元素,TalkBack便可访问文本,但容器内无交互元素,因此不希望将文本设为可聚焦(这也不符合最佳实践)。测试MagentaA11y的示例时,TalkBack可正常导航div内的表格,希望实现相同效果。
目前仅有的临时解决方法是移除tabindex="0"(但这会导致无法用键盘滚动容器),或在区域内添加交互元素(并不理想)。
原因分析
TalkBack对带有tabindex="0"的region地标角色元素存在处理差异:当元素可聚焦时,TalkBack会优先识别其aria-label作为主要标识,默认跳过对内部非交互式内容的遍历——它将可聚焦的region视为独立的"交互容器",而非单纯的内容容器。而MagentaA11y示例中的表格能被正常读取,是因为表格本身属于具有结构化语义的复合控件,TalkBack会特殊处理这类结构化内容的遍历。
解决方法
方法1:用aria-describedby关联容器描述,替代aria-label
保留tabindex="0"和role="region",将aria-label替换为aria-describedby,并添加一个视觉隐藏的描述元素,让TalkBack既能识别容器标识,又能遍历内部内容:
<p>Some content outside container</p> <!-- 视觉隐藏的描述元素,供屏幕阅读器读取容器名称 --> <p id="container-desc" class="sr-only">Container name</p> <div role="region" aria-describedby="container-desc" tabindex="0"> <p>Some content</p> <p>Some content</p> <ul> <li>Some content</li> <li>Some content</li> </ul> </div>
配套的视觉隐藏样式:
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; }
方法2:移除role="region",保留tabindex和aria-label
如果不需要region地标角色的语义(即无需在屏幕阅读器的地标导航中显示该容器),可移除role="region",仅保留tabindex="0"和aria-label,让TalkBack将其视为普通可聚焦容器,从而遍历内部内容:
<p>Some content outside container</p> <div aria-label="Container name" tabindex="0"> <p>Some content</p> <p>Some content</p> <ul> <li>Some content</li> <li>Some content</li> </ul> </div>
方法3:添加不可聚焦的"哨兵"元素触发遍历
在容器内部顶部添加一个空的不可聚焦元素,无需交互性,仅用于触发TalkBack对内部内容的遍历:
<p>Some content outside container</p> <div role="region" aria-label="Container name" tabindex="0"> <span></span> <!-- 哨兵元素 --> <p>Some content</p> <p>Some content</p> <ul> <li>Some content</li> <li>Some content</li> </ul> </div>
此方法无需修改语义属性,仅通过微小DOM调整解决TalkBack的遍历逻辑问题,适合需要保留region地标和aria-label的场景。
内容的提问来源于stack exchange,提问作者baowizard

