一、为什么要构建可访问的组件
在开发web应用程序时,访问性是非常重要的。Web内容应该对所有人都是可用的,无论他们的能力或技术水平如何。这意味着需要确保我们构建的应用程序能访问到各种各样的用户,包括有残障的用户。
如果你需要构建一个级联选择器组件,那么它必须是可访问的,以确保每个人都可以使用它。用户可以使用键盘导航,屏幕阅读器等多种方式来与应用程序交互。
二、如何构建可访问的级联选择器
构建可访问的级联选择器时需要考虑以下问题:
1.表格结构
在使用React构建可访问的级联选择器时,首先需要考虑如何为组件呈现表格结构。
  class CascadingSelect extends React.Component {
    render() {
      const { options } = this.props;
      return (
        <table className="cascading-select">
          <thead>
            <tr>
              <th>Option 1</th>
              <th>Option 2</th>
              <th>Option 3</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>
                <select aria-label="Option 1" className="option-1">
                  {options.map(option => (
                    <option key={option.value} value={option.value}>{option.label}</option>
                  ))}
                </select>
              </td>
              <td>
                <select aria-label="Option 2" className="option-2">
                  <option>Please select an option</option>
                </select>
              </td>
              <td>
                <select aria-label="Option 3" className="option-3">
                  <option>Please select an option</option>
                </select>
              </td>
            </tr>
          </tbody>
        </table>
      );
    }
  }
2.键盘导航
为了使级联选择器对于键盘用户友好,需要进行正确的键盘导航。
使用方向键向上和向下浏览可选项,并且通过键盘选择选项。在此过程中,UI需要提供正确的反馈,例如高亮所选项并将其保存为活动状态。
当选择过程完成后,级联选择器需要使新选择的选项处于活动状态,并将焦点设置为级联选择器下一个可操作单元格中的适当选项。
  class CascadingSelect extends React.Component {
    handleKeyDown = (e, column, row) => {
      const { options, onChange } = this.props;
      const activeOption = options[column][row];
      const activeOptionIndex = options[column].indexOf(activeOption);
      switch (e.keyCode) {
        case 13: // Return
        case 32: // Space
          e.preventDefault();
          onChange(column, row, activeOption.value);
          break;
        case 38: // Up arrow
          e.preventDefault();
          if (activeOptionIndex > 0) {
            onChange(column, row, options[column][activeOptionIndex - 1].value);
          }
          break;
        case 40: // Down arrow
          e.preventDefault();
          if (activeOptionIndex < options[column].length - 1) {
            onChange(column, row, options[column][activeOptionIndex + 1].value);
          }
          break;
        case 37: // Left arrow
          e.preventDefault();
          if (column > 0) {
            const prevRow = options[column - 1].findIndex(option => option.value === options[column - 1][row].parent);
            this[`select${column - 1}-${prevRow}`].focus();
          }
          break;
        case 39: // Right arrow
          e.preventDefault();
          if (column < options.length - 1 && options[column + 1].some(option => option.parent === activeOption.value)) {
            const nextRow = options[column + 1].findIndex(option => option.parent === activeOption.value);
            this[`select${column + 1}-${nextRow}`].focus();
          }
          break;
        default:
          break;
      }
    };
    render() {
      const { options, values } = this.props;
      return (
        <table className="cascading-select">
          ...
          <tbody>
            {options[0].map((option, row) => (
              <tr key={option.value}>
                <td>
                  <select
                    aria-label="Option 1"
                    className="option-1"
                  >
                    {options[0].map(option => (
                      <option key={option.value} value={option.value} selected={values[0] === option.value}>
                        {option.label}
                      </option>
                    ))}
                  </select>
                </td>
                {options.slice(1).map((column, columnIdx) => (
                  <td key={columnIdx}>
                    <select
                      aria-label={`Option ${columnIdx + 2}`}
                      className={`option-${columnIdx + 2}`}
                      disabled={values[columnIdx] === ''}
                      onChange={(e) => this.props.onChange(columnIdx + 1, row, e.target.value)}
                      onKeyDown={(e) => this.handleKeyDown(e, columnIdx + 1, row)}
                      value={values[columnIdx + 1]}
                      ref={(el) => { this[`select${columnIdx + 1}-${row}`] = el; }}
                    >
                      <option value="">Please select an option</option>
                      {column.filter(o => o.parent === values[columnIdx]).map(option => (
                        <option key={option.value} value={option.value} selected={values[columnIdx + 1] === option.value}>
                          {option.label}
                        </option>
                      ))}
                    </select>
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      );
    }
  }
3.屏幕阅读器
屏幕阅读器将表格朗读给残障用户,以使他们能够使用级联选择器。
如果级联选择器中的选项值不像视觉用户那样易于识别,可以添加自定义aria-label来为屏幕阅读器提供更多信息并更好地帮助残障用户使用级联选择器。
  class CascadingSelect extends React.Component {
    render() {
      const { options } = this.props;
      return (
        <table className="cascading-select" aria-label="Cascading select">
          ...
          <tbody>
            {options[0].map((option, row) => (
              <tr key={option.value}>
                <td>
                  <label htmlFor={`option-1-${row}`} className="visually-hidden">Option 1</label>
                  <select
                    id={`option-1-${row}`}
                    aria-label="Option 1"
                    className="option-1"
                  >
                    ...
                  </select>
                </td>
                {options.slice(1).map((column, columnIdx) => (
                  <td key={columnIdx}>
                    <label htmlFor={`option-${columnIdx + 2}-${row}`} className="visually-hidden">Option {columnIdx + 2}</label>
                    <select
                      id={`option-${columnIdx + 2}-${row}`}
                      aria-label={`Option ${columnIdx + 2}`}
                      className={`option-${columnIdx + 2}`}
                      disabled={values[columnIdx] === ''}
                      onChange={(e) => this.props.onChange(columnIdx + 1, row, e.target.value)}
                      onKeyDown={(e) => this.handleKeyDown(e, columnIdx + 1, row)}
                      value={values[columnIdx + 1]}
                      ref={(el) => { this[`select${columnIdx + 1}-${row}`] = el; }}
                    >
                      ...
                    </select>
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      );
    }
  }
三、总结
在React中构建可访问的级联选择器很重要,因为它确保您的web应用程序可以被尽可能多的用户使用。通过使用简单的表格结构,正确定义键盘导航,添加相关的aria-label和label元素,可以容易地创建可访问的级联选择器。
原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/280616.html
 
 微信扫一扫
微信扫一扫  支付宝扫一扫
支付宝扫一扫 