React高级指引:无障碍

21 篇文章 1 订阅
21 篇文章 0 订阅

上一节:以React的方式思考
下一节:代码分割

为什么要使用无障碍辅助功能

网络无障碍辅助功能(也成为a11y)能够被任何人使用的网站功能。无障碍辅助功能是允许辅助性技术解释网站所必需的。

React通过使用标准HTML技术支持构建无障碍辅助网站。

标准和指南

WCAG

网络内容无障碍指南( Web Content Accessibility Guidelines ) 为构建无障碍网站提供了指南。

以下清单概述了WCAG的内容:

WAI-ARIA

网络无障碍协议-无障碍互联网应用(Web Accessibility Initiative - Accessible Rich Internet Applications)包含了构建无障碍JavaScript部件所需的技术。

所有以aria-开头的HTML属性在JSX中都是支持的。然而React中大部分DOM属性都是小驼峰命名格式的(camelCased),但是aria-*则还是按照HTML中的带连字符的命名格式(也称为hyphen-cased、 kebab-case、lisp-case等):

<input
  type="text"
  aria-label={labelText}
  aria-required="true"
  onChange={onchangeHandler}
  value={inputValue}
  name="name"
/>

语义化的HTML

在Web应用中,语义化的HTML是无障碍辅助功能的基础。使用多种HTML来强化网站中的信息通常会让用户直接获得无障碍辅助功能。

有时候我们为了符合React的语法在JSX中加入<div>元素,但这破坏了HTML的语义,这点在使用列表(<ol><ul><dl>)或者<table>时更加突出。在这些场景下我们应该使用React Fragment来组合其他元素。

实例:

 import React, { Fragment } from 'react';

function ListItem({ item }) {
  return (
    <Fragment>
      <dt>{item.term}</dt>
      <dd>{item.description}</dd>
    </Fragment>
  );
}

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        <ListItem item={item} key={item.id} />
      ))}
    </dl>
  );
}

和其他元素一样,你可以把把元素映射到fragement数组中。

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // Fragments should also have a `key` prop when mapping collections
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

如果你的开发工具支持且不需要使用props,你可以使用最短语法:

function ListItem({ item }) {
  return (
    <>
      <dt>{item.term}</dt>
      <dd>{item.description}</dd>
    </>
  );
}

想要了解更多,请查看Fragment

无障碍表单

标签

每一个表单控制,例如<input>,<textarea>,都需要被标记来使用无障碍辅助功能。

下列资源告诉我们如何标注元素:

尽管这些HTML实践可以直接在React中使用,但是for属性在JSX中需要改写成htmlFor

<label htmlFor="namedInput">Name:</label>
<input id="namedInput" type="text" name="name"/>

向用户提醒错误

错误场景需要被所有用户理解。下面的链接向我们展示了如何向用户展示错误文本:

控制焦点

确保你的web应用可以完全使用键盘操作:

键盘焦点及焦点轮廓

键盘焦点的定义是当前DOM中被选中的元素,用于接收用户从键盘输入的信息。它的焦点轮廓与下图展示的类似。
在这里插入图片描述
如果你想要其他的焦点轮廓样式,唯一的办法是使用CSS调整,比如使用outline:0

跳过内容的机制

为了帮助和提速键盘导航,我们提供了一种机制,它允许用户跳过应用中的导航部分。

跳转链接(Skiplinks)或者说跳转导航链接(Skip Navigation Links)是隐藏的导航链接,只有使用键盘导航时他们才可见。使用内部页面锚点和一些样式是很容易就可以实现他们的。

当然我们可以使用地标元素或者角色(如<main><aside>)作为辅助技术来划分页面区域,使得用户可以快速地导航到该区域。

阅读下面的文章来提高对这些元素的认知:

以编程方式管理焦点

React应用在运行过程中会不断地修改HTML DOM,这有时候就导致了键盘焦点的丢失或者是出现了意料之外的元素。为了修复这个问题,我们需要在正确的方向手动地触发键盘焦点。比如在点击按钮打开一个模式窗口后为这个按钮重新设置键盘焦点。

MDN Web 文档关注了这个问题,并向我们说明了如何建立键盘导航JavaScript部件

我们可以使用DOM元素中的Refs在React中设置焦点。

使用Refs,首先我们在class组件中的JSX中为元素添加ref:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建ref来存储input输入元素
    this.textInput = React.createRef();
  }
  render() {
  //使用ref回调来存储input元素的引用
    return (
      <input
        type="text"
        ref={this.textInput}
      />
    );
  }
}

之后我们就可以在组件的任意位置聚焦了。

focus() {
  //显式地调用focus方法
  // 注意:我们通过访问current来获得DOM节点
  this.textInput.current.focus();
}

有时候需要父组件在子组件的元素上设置焦点,我们可以通过将refs暴露给父组件来完成此操作:将父组件的ref通过子组件中特殊的prop转移到子组件的DOM中。

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.inputElement = React.createRef();
  }
  render() {
    return (
      <CustomTextInput inputRef={this.inputElement} />
    );
  }
}

// 现在你可以在需要时调用focus了
this.inputElement.current.focus();

当使用HOC扩展组件时,推荐使用React的forwardRef函数转发到被包裹的组件中。如果第三方HOC不支持转发ref,上面的模式也能够作为回调函数使用。

一个好用的焦点管理例子:react-aria-modal。这是一个相当杰出的无障碍辅助功能完善的模式窗口案例。不仅仅是因为它在取消按钮上设置了初始焦点(防止用户以外触发成功按钮)和把焦点固定在窗口之内,它也会在关闭窗口时将焦点设置到打开窗口的那个元素上。

注意
尽管这是无障碍辅助功能的一个非常重要的特性,但在使用时需要谨慎小心。我们只需要在键盘焦点被打乱时使用它来修复,需要去尝试预测用户的行为。

鼠标和指针事件

确保所有鼠标和指针事件暴露的功能使用键盘事件也可以办到。只依靠指针会导致在许多情况下键盘用户无法使用应用。

为了说明这个,我们来看下由点击事件破坏无障碍辅助功能的典型实例:外部点击模式。用户可以通过点击元素外部来关闭弹出框。
在这里插入图片描述通常实现这个功能的方法是在window对象上绑定点击事件来关闭弹窗:

class OuterClickExample extends React.Component {
  constructor(props) {
    super(props);

    this.state = { isOpen: false };
    this.toggleContainer = React.createRef();

    this.onClickHandler = this.onClickHandler.bind(this);
    this.onClickOutsideHandler = this.onClickOutsideHandler.bind(this);
  }

  componentDidMount() {
    window.addEventListener('click', this.onClickOutsideHandler);
  }

  componentWillUnmount() {
    window.removeEventListener('click', this.onClickOutsideHandler);
  }

  onClickHandler() {
    this.setState(currentState => ({
      isOpen: !currentState.isOpen
    }));
  }

  onClickOutsideHandler(event) {
    if (this.state.isOpen && !this.toggleContainer.current.contains(event.target)) {
      this.setState({ isOpen: false });
    }
  }

  render() {
    return (
      <div ref={this.toggleContainer}>
        <button onClick={this.onClickHandler}>Select an option</button>
        {this.state.isOpen && (
          <ul>
            <li>Option 1</li>
            <li>Option 2</li>
            <li>Option 3</li>
          </ul>
        )}
      </div>
    );
  }
}

对于指针设备比如鼠标来说这样做没问题。但是使用键盘进行此操作时,因为window对象只接收点击事件,用户无法tab到下一个元素,这会导致应用功能不完善,降低用户体验。

在这里插入图片描述同样的功能可以通过使用合适的事件处理权柄来实现,比如onBluronFocus:

class BlurExample extends React.Component {
  constructor(props) {
    super(props);

    this.state = { isOpen: false };
    this.timeOutId = null;

    this.onClickHandler = this.onClickHandler.bind(this);
    this.onBlurHandler = this.onBlurHandler.bind(this);
    this.onFocusHandler = this.onFocusHandler.bind(this);
  }

  onClickHandler() {
    this.setState(currentState => ({
      isOpen: !currentState.isOpen
    }));
  }
   //我们通过使用setTimeout关闭弹出框
   //这是必要的,因为失焦事件在焦点事件前触发,
   //我们需要这个步骤确认这个元素的子节点是否获得焦点
  onBlurHandler() {
    this.timeOutId = setTimeout(() => {
      this.setState({
        isOpen: false
      });
    });
  }
	//如果子元素获得了焦点,就不关闭弹出框
  onFocusHandler() {
    clearTimeout(this.timeOutId);
  }

  render() {
  	//React通过把失焦事件和焦点事件传递给父元素帮助我们
    return (
      <div onBlur={this.onBlurHandler}
           onFocus={this.onFocusHandler}>
        <button onClick={this.onClickHandler}
                aria-haspopup="true"
                aria-expanded={this.state.isOpen}>
          Select an option
        </button>
        {this.state.isOpen && (
          <ul>
            <li>Option 1</li>
            <li>Option 2</li>
            <li>Option 3</li>
          </ul>
        )}
      </div>
    );
  }
}

上述代码把可用功能同时暴露给了指针设备和键盘用户。注意代码中添加的aria-*属性是用来支持屏幕朗读器lang’du’qilang’du’qlang’dulang’dlanglanlal的。为了简单起见,我们这里没有实现用箭头键交互弹出框的功能。
在这里插入图片描述以上只是只依靠指针和鼠标事件破坏键盘用户使用功能的例子之一。所以我们需要经常使用键盘测试以定位问题所在区域,这样我们才能通过键盘感知程序来修复它。

更复杂的部件

复杂的用户体验并不代表它不是易于操作的。通过尽可能接近HTML编程,无障碍访问会变得更容易,甚至复杂的部件也可以通过编码轻易完成。

在这里我们要求掌握ARIA RolesARIA States and Properties。其中包含了许多HTML属性的工具箱。这些HTML属性能被JSX完全支持,使用它们我们可以构建无障碍,功能强大的React组件。

每一个部件都有各自特定的设计模式,并且用户和用户代理都期望能以相似的方式运行它们。

其他需要考虑的点

设置语言

为了屏幕朗读器能够正确使用语音设置,请在页面上正确设置人类语言。

设置文章标题

为了使用户清晰地记住文章的内容,请为文章设置合适的<title>来描述文章内容。

我们可以使用React文章标题组件在React中设置文章标题。

色彩对比度

为使有视觉障碍的用户能清晰地阅读网站上的文字,请确保它们有足够的色彩对比度。

在网站中计算所有的颜色组合是一件非常无聊的事情,所以你可以通过Colorable来计算一个完全无障碍的调色板

在下面提到的aXe和WAVE工具都包含了色彩对比测试,并且会提供对比错误提示。

如果你想扩展你的对比测试能力,你可以使用以下工具:

开发与测试工具

在构建无障碍网站的过程中我们可以使用许多工具。

键盘

到目前为止,最简单也是最重要的检查是测试你的网站能否单独使用键盘操作。操作步骤如下:

  1. 移除你的鼠标
  2. 使用TabShift+Tab浏览
  3. 使用enter激活元素
  4. 需要时,使用箭头键与元素进行交互,比如下拉列表或者菜单

开发辅助

某些无障碍特性我们可以直接在JSX中检验。通常支持JSX的IDE会对ARIA角色,state和属性进行智能感知。我们也可以使用下列工具:

eslint-plugin-jsx-a11y

ESLint中的eslint-plugin-jsx-a11y 插件为你在JSX中的无障碍功能提供了AST语法检测回调。许多IDE允许你直接集成这些反馈到代码分析和源文件窗口。

Create React App集成了这个插件的部分激活规则子集。如果你想要集成更多的无障碍规则,你可以在项目更目录创建.eslintrc文件,文件内容如下:

{
  "extends": ["react-app", "plugin:jsx-a11y/recommended"],
  "plugins": ["jsx-a11y"]
}

在浏览器测试无障碍辅助功能

很多工具可以在浏览器上的网页进行无障碍功能检验。因为它们只能测试你的HTML的技术无障碍行,所以请结合使用下面提到无障碍检测工具使用。

aXe, aXe-core and react-axe

Deque系统提供的aXe-core能够为你的应用提供端到端的自动无障碍检测。这个模块包含了对Selenium的集成。

无障碍引擎或aXe,是根据aXe-core构建的无障碍检测浏览器插件。

你也可以在开发或者debug时使用react-axe模块将无障碍访问的发现打印在控制台上。

WebAIM WAVE

网络无障碍评估工具也是一个无障碍辅助的浏览器插件。

无障碍辅助检测器和无障碍辅助树

无障碍辅助树是DOM树的子集,它包含应该暴露给辅助技术(如屏幕朗读器)的DOM元素的所有无障碍辅助对象。

在某些浏览器中我们可以通过无障碍辅助树来查看每一个元素的无障碍辅助信息:

屏幕朗读器

测试屏幕朗读器应该称为你的无障碍辅助功能测试的一部分。

请注意浏览器和屏幕朗读器的组合很重要。这里推荐在浏览器中测试应用时选择适合的屏幕朗读器。

常用的屏幕朗读器

Firefox的NVDA

NVDA(NonVisual Desktop Access)是广泛使用的开源屏幕朗读器。

如何使用NVDA,请参考下列文献:

Safari的VoiceOver

VoiceOver是苹果设备自带的屏幕朗读器。

如何激活和使用VoiceOver请参考下列文献:

IE的JAWS

JAWS(Job Access With Speech )是在Windows操作系统中常用的屏幕朗读器。

如何使用JAWS请参考下列文献:

其他屏幕朗读器

谷歌Chrome的ChromeVox

ChromeVox是Chromebook自带的屏幕朗读器,同时也是Google Chrome的一个插件。

如何使用ChromeVox请参考下列文献:

上一节:以React的方式思考
下一节:代码分割

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值