[译]React高级指引1:无障碍

原文连接:reactjs.org/docs/access…html

为何要使用无障碍辅助功能

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

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

标准和指南

WCAG

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

如下清单概述了WCAG的内容:github

WAI-ARIA

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

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

<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请参考下列文献:

相关文章
相关标签/搜索