Web Accessibility 入门指南:从零构建无障碍人机交互体验

2次阅读
没有评论

共计 2883 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

为什么我们需要关注无障碍访问?

很多开发者可能没有意识到,我们日常开发的网页对部分用户来说可能是难以使用的。比如:

Web Accessibility 入门指南:从零构建无障碍人机交互体验

  • 色盲用户无法区分红色错误提示和绿色成功提示
  • 运动障碍用户无法使用鼠标,但网页的所有功能却只能通过鼠标操作
  • 屏幕阅读器用户听到的是混乱的、没有语义结构的页面内容

根据 WHO 统计,全球有超过 10 亿人患有某种形式的残疾。忽视无障碍性意味着我们主动将这部分用户拒之门外。更现实的是,许多国家和地区已将无障碍访问作为法律要求,比如美国的 Section 508 和欧盟的 EN 301 549 标准。

理解无障碍标准 WCAG

Web 内容可访问性指南(WCAG)2.1 是目前最广泛采用的标准,分为三个级别:

  1. A 级(最低要求):比如为非文本内容提供文本替代
  2. AA 级(推荐标准):比如颜色对比度至少达到 4.5:1
  3. AAA 级(高要求):比如颜色对比度达到 7:1

对于大多数项目,达到 AA 级就是不错的目标。AA 级中几个关键要求包括:

  • 键盘可操作:所有功能都能通过键盘访问
  • 足够的对比度:文本与背景的对比度至少 4.5:1
  • 明确的状态指示:比如表单错误不仅用颜色标记,还要有文字说明

WAI-ARIA 核心三要素

当 HTML 语义不足时,WAI-ARIA(Accessible Rich Internet Applications)规范提供了补充方案,重点关注三个属性:

  1. role(角色):定义元素的类型,如role="button"
  2. aria-label/labelledby(标签):提供可访问的名称
  3. aria-* 状态 / 属性:如 aria-expanded="true" 表示展开状态

实战:构建无障碍 React 组件

下面是一个带键盘导航的菜单组件示例(TypeScript):

interface MenuItem {
  id: string;
  label: string;
  onClick: () => void;}

interface AccessibleMenuProps {items: MenuItem[];
  label: string;
}

export function AccessibleMenu({items, label}: AccessibleMenuProps) {const [isOpen, setIsOpen] = React.useState(false);
  const [activeIndex, setActiveIndex] = React.useState(0);
  const menuRef = React.useRef<HTMLDivElement>(null);

  // 管理键盘导航
  const handleKeyDown = (e: React.KeyboardEvent) => {if (!isOpen) return;

    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setActiveIndex((prev) => (prev + 1) % items.length);
        break;
      case 'ArrowUp':
        e.preventDefault();
        setActiveIndex((prev) => (prev - 1 + items.length) % items.length);
        break;
      case 'Enter':
      case 'Space':
        e.preventDefault();
        items[activeIndex].onClick();
        setIsOpen(false);
        break;
      case 'Escape':
        setIsOpen(false);
        break;
      case 'Tab':
        setIsOpen(false);
        break;
    }
  };

  // 确保焦点在菜单打开时正确管理
  React.useEffect(() => {if (isOpen && menuRef.current) {const firstItem = menuRef.current.querySelector('[role="menuitem"]') as HTMLElement;
      firstItem?.focus();}
  }, [isOpen]);

  return (
    <div 
      ref={menuRef}
      role="menu" 
      aria-label={label}
      onKeyDown={handleKeyDown}
    >
      <button 
        aria-haspopup="true"
        aria-expanded={isOpen}
        onClick={() => setIsOpen(!isOpen)}
      >
        {label}
      </button>

      {isOpen && (
        <ul role="menu">
          {items.map((item, index) => (
            <li
              key={item.id}
              role="menuitem"
              tabIndex={-1}
              aria-current={activeIndex === index}
              style={{ 
                background: activeIndex === index ? '#f0f0f0' : 'transparent',
                outline: 'none' 
              }}
              onClick={() => {item.onClick();
                setIsOpen(false);
              }}
              onMouseEnter={() => setActiveIndex(index)}
            >
              {item.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

这个组件实现了:

  1. 完整的键盘导航(上下箭头选择,Enter/Space 确认)
  2. 正确的 ARIA 属性标记
  3. 焦点管理
  4. 跳过链接(Skip Link)支持

自动化检测工具:axe-core

除了手动测试,我们可以使用 axe-core 进行自动化检测:

  1. 安装 axe-core 浏览器插件或 npm 包
  2. 在开发环境中运行检测:
import axe from 'axe-core';

// 在测试中运行
axe.run(document.body, {}, (err, results) => {if (err) throw err;
  console.log(results.violations); // 输出可访问性问题
});

三个常见反模式

  1. 滥用 aria-hidden
  2. 错误做法:<div aria-hidden="true"> 重要内容 </div>
  3. 正确做法:只对纯装饰性内容使用

  4. 缺失 alt 文本的装饰性图片

  5. 错误做法:<img src="decorative.jpg">
  6. 正确做法:<img src="decorative.jpg" alt=""role="presentation">

  7. 自定义控件缺少键盘支持

  8. 错误做法:只实现鼠标点击事件
  9. 正确做法:同时处理键盘事件(Enter/Space)

进阶测试建议

  1. 使用屏幕阅读器测试
  2. Mac:内置 VoiceOver(Cmd+F5 激活)
  3. Windows:NVDA(免费)或 JAWS(商业)

  4. 纯键盘测试

  5. 尝试只用 Tab/Shift+Tab/ 箭头键操作整个页面

  6. 高对比度模式测试

  7. 在 Windows 设置中启用高对比度主题

资源推荐

记住,无障碍性不是一次性的工作,而应该成为开发流程的一部分。从今天开始,试着在每次代码审查时都问一句:这部分对残障用户友好吗?

正文完
 0
评论(没有评论)