如何强制 Voiceover (MacOS) 正确读出美元金额,忽略内部跨度元素?

How can I force Voiceover (MacOS) to read out a dollar amount correctly, ignoring inner span elements?

提问人:Charlie Taylor 提问时间:11/4/2023 最后编辑:Charlie Taylor 更新时间:11/13/2023 访问量:95

问:

我有这个内容(简化):

<div>
  $<span class="enlarged-text">245</span>.50
</div>

当我在 Safari (MacOS Monterey 12.7.1) 上使用 Voiceover 时(阅读下一个元素),它会根据需要将其读出为“二百四十五美元五十美分”。CAPSLOCK + Right

然而,使用画外音的自动读出功能 - 使用画外音启动 - 画外音会很奇怪地读出“两美元,四十五点五零”。有没有办法解决这个问题?CAPSLOCK + A

我已经在周围的 div 上尝试了未记录的内容,它有所帮助,但使用自动读数读出“二百四十五美元,五十”。使用它读作“二百四十五美元点五零”。role="text"CAPSLOCK + Right

<div role="text">
  $<span class="enlarged-text">245</span>.50
</div>

我还尝试将每件作品单独包围起来,但没有任何区别:span

<div role="text">
  <span>$</span><span class="enlarged-text">245</span><span>.50</span>
</div>
HTML 用户界面辅助 功能 画外音 屏幕阅读器

评论

0赞 QuentinC 11/7/2023
您可以发布放大文本的 CSS 和/或将哪些样式应用于父 div 和 span 吗?你可能会觉得这很奇怪,但有时 VoiceOver 会受到看似无辜的不相关的 CSS 的影响。谢谢。
0赞 Charlie Taylor 11/13/2023
这太奇怪了!不幸的是,我能够仅使用上面显示的 HTML 重现该问题,因此我怀疑这不是问题所在。
0赞 QuentinC 11/13/2023
好吧,太糟糕了。另一个问题:您是否尝试过使用最新的MacOS?而在 iOS 上?只是为了找出它是一般的 VoiceOver,还是仅在 Mac 上。如果它在 iOS 上运行良好,我不会感到惊讶,因为 MacOS 上的奇怪错误比 iOS 多得多。
0赞 Charlie Taylor 11/13/2023
谢谢@QuentinC。是的,这是一个公平的问题,我还没有进入 iOS,不。拭目以待。关于macos版本的一点是一个很好的问题,我团队中的某个人尝试在他们未更新的设备上进行测试,但结果更糟,我记得😅感谢您与我一起调试。我同意,我可能只需要找到一个折衷方案,满足于一些中规中矩的东西,因为 Mac 上的画外音似乎在某些方面最终有点缺陷。
0赞 QuentinC 11/14/2023
我没有想法,所以如果你想做某事,你能做的最好的事情就是使用 VonC 的答案中给出的视觉隐藏模式。实际上,您需要这样做是可悲的,因为您的 HTML 实际上没有什么特别之处。或者你可以什么都不做,并考虑一下,无论如何,这种屏幕阅读器的怪癖很常见,盲人用户已经习惯了它们,并且仍然会理解。

答:

2赞 VonC 11/6/2023 #1

由于您在使用 Voiceover 的自动读出功能时遇到不一致的行为,请尝试使用该属性显式指定 Voiceover 应读出的文本(ARIA:可访问的富 Internet 应用程序)。aria-label

<div aria-label="245 dollars and 50 cents">
  $<span class="enlarged-text">245</span>.50
</div>

您还可以使用 aria-hidden 属性来隐藏导致屏幕阅读器读出不正确的元素,并添加包含屏幕阅读器正确格式的视觉隐藏。span

<div>
  <span aria-hidden="true">$<span class="enlarged-text">245</span>.50</span>
  <span class="visually-hidden">245 dollars and 50 cents</span>
</div>

.visually-hidden是一个类,它在视觉上隐藏内容,但使屏幕阅读器可以访问它。
它将被定义为:

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

(摘自“CSS in Action / Invisible Content Only for Screen Reader Users”(CSS在行动/仅供屏幕阅读器用户使用的不可见内容")

您也可以尝试将 CSS 属性与 ::before 或 ::after 伪元素一起使用来注入货币符号,这可能不会干扰 Voiceover 的读出逻辑。content

<div class="price">
  <span class="enlarged-text">245</span>.50
</div>
.price::before {
  content: "$";
}

正如 QuentinC评论中指出的那样

属性,并且仅适用于交互式/可聚焦元素和地标,或具有此类角色的元素。因此,在这种情况下,它将不起作用。aria-labelaria-labelledby

实际上,有时可以在非交互式元素上使用以提供可访问的名称,但其支持在不同的屏幕阅读器和方案中确实可能不一致。aria-label

目标仍然是确保屏幕阅读器正确地将美元金额解释为整数后跟美分,而不会被 HTML 结构误导。

您可以尝试将美元符号和数字组合在一个没有视觉空间的单个中,然后使用 CSS 在视觉上仅放大美元金额部分。span

<div>
  <span aria-hidden="true" class="price">$245<span class="cents">.50</span></span>
  <span class="visually-hidden">245 dollars and 50 cents</span>
</div>
.price .cents {
  font-size: smaller;
}

或者,如果无法修改视觉外观,则可以使用 JavaScript 在页面加载或内容更改时根据跨度的内容动态设置属性。aria-label

window.onload = function() {
  var priceDiv = document.querySelector('.price-container');
  var price = priceDiv.innerText;
  priceDiv.setAttribute('aria-label', price.replace('$', '') + ' dollars and ' + price.split('.')[1] + ' cents');
};
<div class="price-container" role="text">
  $<span class="enlarged-text">245</span>.50
</div>

JavaScript 方法动态操作,确保标签始终设置为需要读出的正确美元金额。aria-label

如果由于 Voiceover 的特定怪癖,上述解决方案都不起作用,请按照 QuentinC 的建议检查是否有任何现有的 CSS 规则干扰屏幕阅读器正确解释内容的能力。


很遗憾,我宁愿拥有视觉和屏幕阅读器用户都能阅读的一个元素。否则,内容很容易不同步。

为了降低内容不同步的风险,您可以使用 JavaScript 根据可见内容动态更新视觉上隐藏的文本。
这样,对可见价格的任何更改都会自动反映在屏幕阅读器文本中。

除了可见内容外,还使用视觉上隐藏的跨度。

<div id="price-container">
    $<span class="enlarged-text">245</span>.50
    <span class="visually-hidden" id="accessible-price"></span>
</div>

确保屏幕阅读器可以访问它,但不可见。

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    border: 0;
}

动态设置视觉上隐藏的跨度的内容。

function updateAccessiblePrice() {
    var priceText = document.getElementById('price-container').innerText;
    var accessiblePrice = priceText.replace('$', '') + ' dollars and ' + priceText.split('.')[1] + ' cents';
    document.getElementById('accessible-price').innerText = accessiblePrice;
}

// Initial update
updateAccessiblePrice();

// Update when the content changes (e.g., via AJAX, user action, etc.)
// That needs to be triggered appropriately depending on how your content updates

这种方法将确保屏幕阅读器文本始终与可见文本同步。

它需要一些额外的努力来设置,但它有效地解决了保持屏幕阅读器阅读的内容和视力正常的用户看到的内容之间的一致性的问题。

评论

1赞 QuentinC 11/7/2023
属性 aria-label 和 aria-labelledby 仅适用于交互式/可聚焦元素和地标,或具有此类作用的元素。因此,在这种情况下,它将不起作用。
0赞 VonC 11/7/2023
@QuentinC 好点子,谢谢你的反馈。为了提高知名度,我加入了您的评论,并提出了一些替代方案。
0赞 Charlie Taylor 11/13/2023
感谢您的建议@VonC,如果没有人知道一种简单的方法可以做得更好,那么我怀疑我将不得不使用您在开始时提到的视觉上隐藏的第二个跨度。很遗憾,我更喜欢视觉和屏幕阅读器用户都能阅读的一个元素。否则,内容很容易不同步。
0赞 VonC 11/13/2023
@CharlieTaylor好的。我已经编辑了答案以包含动态更新方法,这里可能会对此感兴趣。
0赞 Damian Chudobiński 11/13/2023 #2

使用 JavaScript,你可以让它有点动态,但一点也不完美

window.onload = function() {
  var priceDiv = document.querySelector('.price-container');
  var price = priceDiv.innerText;
  priceDiv.setAttribute('aria-label', price.replace('$', '') + ' dollars and ' + price.split('.')[1] + ' cents');
};