使用 visibility hidden 进行可访问性适配【翻译】

66 阅读4分钟

使用 visibility hidden 进行可访问性适配

原文:www.scottohara.me/blog/2022/1…

原文日期:2022.11.07

原文作者:Scott O'Hara

翻译日期:02/04/23 12:17:05 CST

虽然可以通过 CSS 媒体查询适配不同的屏幕尺寸和缩放等级来适配内容,但如果在视觉显示之外不做更多的调整,结果有时是不甚理想的。

有时在正常网页中的内容,需要用对话框的形式在小屏设备中呈现。或者,在小屏上展示/隐藏是有意义的,但是切换到大屏幕上就不必了。

HTML 提供了一些特性,例如 source 元素的 media 属性,这个特性被用在 picture 元素上,以检测不同的尺寸的媒体查询状态进行适配。然而这个特性没有扩展到其它元素上。(也许其它元素也应该有这个元素?)

我有一些这类问题的实例,我们现在先列举一例。

使用媒体查询来切换

设想你有个 FAQ(常见问题)页面,在大屏设备上展示所有常见问题,每个问题都能在屏幕上直接预览,因此目前问题还不够明显。然而,换到小屏设备,为了帮助人们减少滚动来找到想要的答案,所有的回答默认都被折叠了。

因此大屏幕上的网页会像下面这样,一个标题下面跟着一个答案:

<h#>
  Frequently asked question
</h#>
<div>Answer goes here!</div>

但是小屏上我们期待像下面这样:

<h#>
  <button aria-expanded="...">Frequently asked question</button>
</h#>
<div>Answer goes here!</div>

目前,我们不想根据屏幕尺寸重做 DOM 展示不同的 UI。我们也不能只在大屏上去掉按钮的视觉效果,因为这样做,底层的隐式和显式的无障碍(ARIA)语义仍然暴露给了(无障碍)辅助技术。而且,不考虑样式,按钮的功能性和可聚焦性还是会被保留。

人们也许会考虑使用 JavaScript 基于屏幕尺寸来添加/移除按钮。这还是太麻烦了,另一个办法是通过添加/移除按钮的属性来抑制按钮的语义和行为。这么做,最终你会在屏幕上看到下面这一堆冗余标签:

<h#>
  <button aria-expanded="..."
  	disabled
  	role="none"
  	>Frequently asked question</button>
</h#>
<div>Answer goes here!</div>

属性 disabled 用于完全移除按钮的可聚焦性(tabindex=-1 只是设置了无法通过 tab 键聚焦,但仍然是可聚焦的)。另外还要设置样式,让按钮看起来不是 disabled 的状态。最后再设置 role=none 来让按钮的隐式的按钮角色(role)和不可用状态(state)之间不再有关联。

这是漫长又曲折的路途,即使我们没有添加或移除按钮,也带来了大量的 DOM 操作。

使用 CSS 和 visibility

不修改 DOM,我们可以用 CSS 媒体查询来控制展示或隐藏 button 元素。

看下面代码:

<h3>
  <button aria-expanded=...>
    <span>My text</span>
  </button>
</h3>
<div class=content>
  the content that toggles depending on breakpoint.
</div>

和上面介绍的小屏幕方案代码没有太大区别,但 button 元素包裹了 span 是做什么的?

好吧,这就是 CSS 的 visibility 属性 发光发热的地方。

属性 visibility 很有意思。当值被设置为 hidden 的时候,元素的内容视觉上被隐藏了,但是仍然在页面占据“空间”。页面上的一片空白就像应用了 opacity: 0。但是不同于 opacityvisibility: hidden 不会暴露给浏览器的可访问性(accessibility) API。这种行为很像 display: none,它们都不会被展示在 a11y 树中。

在本例中,让 visibility 既不同又实用的是,你可以让被应用visibility: hidden的子树中的某个部分再次可见。

在大尺寸屏幕中,按钮要隐藏,内容要可见。见下方代码:

@media screen and ( min-width: 600px ) {
  button[aria-expanded] {
    visibility: hidden;
  }
}


button[aria-expanded] span {
  visibility: visible;
}

上方媒体查询中确保了带有aria-expanded属性的按钮会在比 600px 宽的屏幕上隐藏。这确保了按钮不会暴露给辅助技术。

上面代码的第二个部分,确保了按钮的子元素,也就是 span 元素中的内容,总是会被展示,而不管 button 元素是否隐藏。

还需要一点 JavaScript 来完成这个 disclosure 组件(我还在另一篇文章中提到这个组件),你可以点击查看在线代码

const b = document.querySelector('button');
const c = document.querySelector('.content');

b.addEventListener('click', function (e) {
  if ( this.getAttribute('aria-expanded') === 'true' ) {
    this.setAttribute('aria-expanded', 'false');
    c.classList.add('hidden');
  }
  else {
    this.setAttribute('aria-expanded', 'true');
    c.classList.remove('hidden');
  }
});

难道不觉得有点 hacky 和困惑吗?

当然。但是想要不过度依赖 JS 甚至 CSS 而通过原生 HTML 标签,在禁用 JS 和 CSS 的时候仍然可用,目前还没有很好的方案。例如,这些 JS 资源文件加载失败的时候,服务器出现问题的时候,打开某种阅读模式的时候,就不能使用上面的无障碍方案了。

Extra effort would need to be taken (likely with JavaScript) to make this behave in a nice progressively enhanced sort of way. But, I’m content with just being clever today. Maybe I, or someone else, can figure out how to make this a smart idea some other time.