面向用户的状态

Avatar of Scott O'Hara
Scott O'Hara 发表

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200美元免费额度!

让我们谈谈状态。指的是向用户传达状态,而不是应用程序在 JavaScript 对象或 localStorage 中存储状态。我们将讨论如何让用户了解状态(例如,按钮是否被禁用,或者面板是否处于活动状态),以及如何使用 CSS 来实现。出于即将阐明的原因,我们不会使用内联样式,或者尽可能避免使用类选择器。

还在看?不错。让我们开始吧。

应用程序的所有动态组件都具有默认的面向用户的状态,并且需要随着用户与这些组件交互而存储和更新该状态。

例如,当按下按钮时,会发生一些事情(这就是按钮的用途)。当这些事情发生时,它们通常以界面中的视觉方式表示。按钮的背景可能会更改以指示它已被按下。如果按钮控制界面中的其他组件,则这些组件的样式可能会发生视觉变化,或者在某些情况下会切换其可见性。项目被删除,弹出通知,应用错误样式等。

您可能已经注意到,我们一直在多次提及组件的“视觉”状态。这正是我在许多教程、文章和关于状态的一般讨论中发现的问题类型。

通常,开发人员使用“有状态”类来管理组件的状态。但这非常不足,因为组件不仅仅由其外观组成。需要与组件的视觉表示一起管理底层语义。一旦您通过键盘和/或屏幕阅读器与之交互,就会发现未能管理这些底层语义的问题。

本文重点介绍如何适当地传达状态,以便除视力正常、使用鼠标的用户之外,所有用户都可以与我们的界面进行交互。

状态不仅仅是外观

除了使用 CSS 适当地隐藏内容 以便于视力正常用户和辅助技术之外,CSS 对元素的语义或可访问状态没有太多有意影响。我的意思是,除了像 不受支持的“speak”、before/after 伪内容以及根据用户偏好专门重新设置组件样式的媒体查询(如 减少运动媒体查询 和其他提议的 用户查询)之外,CSS 本身并非旨在更改元素的语义、内容或以有意义的方式适当地传达元素的状态。

我为什么要提起所有这些?因为仅使用 CSS 类来管理状态对于向所有用户传达状态来说,大多数情况下都是不足的。作为一种用于展示目的的语言,为输入赋予 .has-error 类以将边框颜色更改为红色阴影,对它没有任何语义价值。就 CSS 而言,“这就是您想要设置该输入的样式。不错。我支持您!只是不要要求我在 DOM 中向上设置样式。我在这里划清界限,伙计……”

相反,为了管理和传达状态,我们应该更新相应元素上的属性。不,我指的不是 data 属性。这些也没有任何意义。如果我们采用这种方法,在许多情况下,我们甚至不需要有状态的类,除了切换元素 display 的类之外。

我们是否忘记了可以使用属性选择器设置样式?

HTMLARIA 有一系列属性,应该用于适当地传达组件的当前状态。

考虑在您的 <button></button><input type="text" /> 上使用 .is-disabled 类?这只会视觉上禁用它。您仍然必须以编程方式关闭该元素的点击和键盘事件。相反,使用 [disabled] 属性,您将拥有一个用于设置元素样式的 CSS 选择器,浏览器将为您完成所有适当的工作以禁用该元素!

所以,不要使用

input.is-disabled { 
  opacity: .65; 
}

而是使用

input[disabled] { 
  opacity: .65; 
}

这与使用 .is-disabled 类实现了相同的视觉效果,但我们利用了我们需要设置的属性的属性选择器,以便向浏览器和用户传达当前状态。同时,如果我们只是切换类,则无需执行上述任何额外工作(使用 JavaScript)来禁用输入。

示例:处于“活动”状态

为了提供一些更深入的上下文,让我们来看一个您可能使用 .is-active 类的场景。对于不同的组件,“活动”可能意味着完全不同的东西,这就是为什么我可以理解想要使用单个可重用类名,而不是确定需要管理哪个属性以适当地传达状态。但使开发人员更容易管理状态并不一定会帮助用户,所以让我们以正确的方式来做。

活动导航链接

首先,让我们看看如何在导航中声明当前活动的链接。以下 Pen 包含两个示例。第一个使用 .is-active 类来指示当前导航项。第二个使用 aria-current="page"

查看 Pen .is-active 与 aria-current=’page’,作者:Scott (@scottohara),来自 CodePen

虽然它们看起来完全相同,但如果您使用 Jaws 18、Voice Over 或 NVDA 2017.2(发布时)导航示例,您会听到类似以下内容:“功能,当前页面。”当使用 aria-current 与示例交互时。查看 Léonie Watson 关于 [aria-current] 的文章,了解可以在其中使用此属性进行样式设置(代替 .is-active 类)的其他许多示例。

活动按钮

根据按钮的目的,可能需要通过以下 ARIA 属性之一来增强按钮的活动状态,以供屏幕阅读器用户使用

  • aria-expanded – 指示按钮控制界面中的另一个组件,并传递该组件的当前状态。
  • aria-pressed – 指示按钮的行为类似于复选框,因为它将其状态在按下或未按下之间切换。

如果不使用上述属性之一,则按钮没有内在的方式来传达它是否已被交互过。如果某种情况不需要这样做,那完全没问题,但如果您确实需要传达按钮已被激活,则可以使用 aria-pressed 来实现。

查看 Pen 切换按钮示例,作者:Scott (@scottohara),来自 CodePen

在上面的示例中,我们有一个可以与之交互的按钮,用于将项目添加到购物车。为了指示何时添加了项目,我们没有使用类,而是切换 aria-pressed 属性的布尔值,并使用 [aria-pressed="true"] 作为我们的样式钩子来视觉上传达活动状态。当通过屏幕阅读器与按钮交互时,它将被宣布为“已选中”或“未选中”、添加到购物车、切换按钮。

要深入了解在开发可访问的切换按钮时应考虑的事项,只需查看 Heydon Pickering 的切换按钮文章 即可。Heydon 详细概述了为什么更改按钮的可见标签不是一个好主意,甚至指出了您可能实际上并不想要切换按钮,而是应该考虑使用 开关

管理手风琴状态

对于我们的最后一个示例,让我们看看如何在手风琴组件中管理状态

查看 Pen ARIA 手风琴示例,作者:Scott (@scottohara),来自 CodePen

如果你仔细阅读 CSS 和 JavaScript 中的注释,你会注意到这个演示做了一些事情。

首先,手风琴的标记模式的构建方式使得即使 JavaScript 因任何原因被禁用,用户也不会无法访问任何内容,因为只有在存在 .js 类的情况下,面板才会被隐藏。

其次,为了避免在每个手风琴面板标题中使用实际的 <button></button> 元素,我们改为将它们嵌套的 <a> 转换为“按钮”,方法是应用 ARIA role="button",然后通过键盘按下事件监听器添加所有预期的键盘功能。此外,为了确保键盘用户可以访问“按钮”,已将 tabindex="0" 设置为每个 ARIA 按钮。

最后,我们在这里使用 aria-expanded 属性来传达手风琴面板的当前状态,因此当用户使用屏幕阅读器将焦点放在手风琴触发器上时,它会宣布“手风琴标题,已折叠(或展开)按钮”。

你会注意到手风琴面板正在利用 .is-active 类来切换其可见状态。天哪!但是等等,这是我们**可以**依靠 CSS 独自帮助我们的一件事。如果我们仔细看看这里使用的选择器

.js .accordion__panel {
  border-bottom: 1px solid;
  overflow: hidden;
  padding: 0 1em;
  max-height: 0px;
  transition:
    max-height .2s ease-in-out,
    visibility .2s ease-in-out;
  visibility: hidden;
}

.js .accordion__panel.is-active { 
  max-height: 100vh;
  visibility: visible;
}

第一个选择器,即依赖于 JavaScript 可用的那个,利用 visibility: hidden 包容性地隐藏面板的内容,使其对有视力用户和辅助技术用户都不可见。然后设置溢出、最大高度和过渡属性以折叠面板,并在 .is-active 类添加到手风琴面板后,准备将其扩展为展开形式。我本可以切换 display: none 或以编程方式添加和删除面板的 hidden 属性,但这样会失去面板打开过渡的能力。而且每个人都喜欢好的过渡,对吧?

结束语

我主要希望你从所有这些中获得的启示是,如果你只是切换类来直观地管理组件的状态,那么你可能没有适当地向辅助技术用户传达该状态。

你需要使用合适的元素(<button></button> 是你的朋友!),并管理合适的属性及其值,以创建真正可访问的用户体验。当然,你可以执行这些操作继续切换状态类来控制你的样式。但是,如果我们必须更新属性及其值,并且这些也是有效的 CSS 选择器,那么为什么要通过切换类来做更多不必要的工作呢?