问题:
我们正在从对话框中的一个按钮触发一个小吃条,但该小吃条没有被读取。当从对话框外的按钮触发时,它的读数很好。
所需行为:
当用户点击";打开SNACKBAR";对话框中的按钮屏幕阅读器读取小吃条。
Codesandbox:
https://codesandbox.io/s/dialog-snackbar-testing-sje9p?file=/demo.js
重现问题的步骤:
注意:如果你想看看屏幕阅读器应该如何阅读零食条,请单击第一个"打开SNACKBAR";按钮
- 打开NVDA屏幕阅读器或JAWS屏幕阅读器
- 转到https://codesandbox.io/s/dialog-snackbar-testing-sje9p?file=/demo.js
- 单击";打开对话框";按钮
- 单击";打开SNACKBAR";按钮
测试详细信息:
- 按钮触发:爪+铬=通过
- 按钮触发:JAWS+Firefox=通过
- 按钮触发器:爪+边缘=通过
- 按钮触发器:NVDA+Chrome=PASS
- 按钮触发:NVDA+Firefox=通过
- 按钮触发器:NVDA+Edge=PASS
- 对话框按钮触发器:JAWS+Chrome=FAIL
- 对话框按钮触发器:JAWS+Firefox=失败
- 对话框按钮触发器:JAWS+Edge=FAIL
- 对话框按钮触发器:NVDA+Chrome=FAIL
- 对话框按钮触发器:NVDA+Firefox=失败
- 对话框按钮触发器:NVDA+Edge=FAIL
我认为你很幸运,你的第一个例子成功了。在这两个测试用例中,似乎都没有正确使用aria-live
。aria-live
意味着存在于页面上,并且而不是本身被动态添加。
也就是说,为了使aria-live
始终正确工作,无论是哪个浏览器、屏幕阅读器还是平台,您的页面都应该是这样的:
之前
<div>stuff</div>
<div aria-live="polite"> <!-- intentionally empty --> </div>
<div>stuff</div>
当您添加snackbar消息时,它将被注入到空的<div>
中,并被正确地宣布。
之后
<div>stuff</div>
<div aria-live="polite"> snackbar stuff </div>
<div>stuff</div>
不能可靠工作的是动态添加aria-live
元素。以下内容可能适用于某些浏览器+屏幕阅读器组合,也可能不适用:
之前
<div>stuff</div>
<div>stuff</div>
之后
<div>stuff</div>
<div aria-live="polite"> snackbar stuff </div>
<div>stuff</div>
请注意,我的示例直接使用aria-live
。在您的示例中,您的小吃条使用role="alert"
,这为您提供了一个隐含的aria-live="assertive"
。为了宣布文本,如果你有";礼貌的";或";自信";。主要问题是动态添加一个role="alert"
(即aria-live
)。它通常不起作用。
事实上,你听到的是按钮示例,而不是对话框示例,这很奇怪,但你必须消除的第一件事是动态aria-live
。如果你这样做了,并且仍然在这两种情况下都不起作用,我可以再看一眼。
问题是页面上有多个活动区域。
当您添加第一个小吃条时,您将暴露role="alert"
(实际上与aria-live="assertive"
相同)。
这应该在所有浏览器中都能很好地工作(一个警报)。
然而,在众多的屏幕阅读器中,多个aria-live
区域往往会失败,尤其是在动态添加和删除时。
处理此问题的最佳方法是将所有屏幕阅读器警报移动到一个单独的aria-live
区域,并设置一个消息队列来处理应用程序级别的警报,这样您就只有一个aria-live
区域(如果您需要礼貌和自信的选项,则为2个),不会遇到那么多问题。
快速示例/概念
HTML
<!-- You can add and remove these at will, this is the dynamically added snackbar -->
<div class="snack"></div>
<!-- This will **always** exist on the page and we will update the contents from our message queue -->
<div id="alertAnnouncer" aria-live="assertive">
</div>
伪代码
// Adds the snack bar with the specified text. This has nothing to do with the screen reader message queue, it is just to represent your current function to add a snack bar.
addSnackBar("Alert Text");
// this is how we add messages to the queue.
addScreenReaderAlert("Alert Text");
// we hold our messages here for processing.
var messageQueue = [];
function addScreenReaderAlert(textToAnnounce){
messageQueue.push(textToAnnounce);
//CODE TO ADD: start the interval to process the queue if it is not running, if no update has happened in the last x seconds process the message immediately and set the last time processed or something similar.
}
function processMessageQueue(){
if(messageQueue.length > 0){
//CODE TO ADD: get oldest message (you would use slice etc. and remove it entirely from the queue)
// ultimately we want this
let oldestMessage = messageQueue[0];
// we change the inner HTML of the aria-live region so the oldest message is announced.
return document.querySelector('#alertAnnouncer').innerHTML = oldestMessage;
}else{
//CODE TO ADD: clear the interval
}
//NOTE: to save CPU cycles I would stop the interval once the message queue reaches 0 length and then restart the message queue interval when you fire the addScreenReaderAlert() function.
}
// this is purely to show there is an interval on the queue, you would set and unset this as described above.
Window.setInterval(processMessageQueue, 1000);
// NOTE: The interval delay should be set to a value large enough to allow a screen reader to announce the messages. So for a simple snackbar this can be shorter, but if you want to announce longer messages you need to set this higher. Note that the interval is purely for spreading out multiple messages added in quick succession so you don't overwrite them too quickly.
代码说明
这一切看起来既复杂又混乱,我很抱歉!
但本质上,我们想要一种方式:
- 向队列添加消息
- 对CCD_ 17区域的每次更新之间具有间隙的队列进行处理
- 理想情况下,如果在最后x秒内没有消息,我们会立即处理项目
消息之间的间隔是为了确保您不会覆盖当前正在讲话的消息。
您可以设置aria-live
区域,以便继续向其中添加消息(附加消息而不是替换),但这在所有屏幕读取器中并不总是稳定的(旧的NVDA、ORCA等并不总是在一定长度后读取新项目)。一旦你微调消息计时等,这个解决方法就非常强大。
消息队列,尽管一开始需要设置一些工作,但也是一种非常简单的方式来管理整个应用程序中的所有警报等,而不必担心多个警报重叠等。
如果你需要更多的解释(因为我不确定我在这个解释中是否"含糊其辞"🤣)请告诉我。