无障碍, 包容性·

Web 无障碍访问

让所有人都能使用 Web——无障碍访问的重要性与实现方法

Web 应该为所有人服务

全球有超过 10 亿人患有某种形式的残疾。视力障碍、听力障碍、运动障碍、认知障碍——这些用户同样需要访问 Web 内容和服务。无障碍访问(Accessibility,简称 a11y)不是锦上添花,而是必要条件。

W3C 的 Web Accessibility Initiative(WAI)制定了一系列标准,帮助开发者构建可访问的 Web 内容。其中最重要的是 WCAG(Web Content Accessibility Guidelines)

WCAG 四大原则

WCAG 2.1 定义了四个基本原则,每个原则都有具体的成功标准:

可感知

信息和 UI 组件必须能被用户感知

  • 文本替代
  • 时间媒体替代
  • 可适应
  • 可区分

可操作

UI 组件和导航必须可操作

  • 键盘可访问
  • 足够的时间
  • 不引起癫痫
  • 可导航

可理解

信息和 UI 操作必须可理解

  • 可读
  • 可预测
  • 输入协助

健壮性

内容必须足够健壮,能被各种用户代理可靠地解释

  • 兼容性

语义化 HTML

使用正确的 HTML 标签是无障碍访问的基础:

<!-- 正确:使用语义化标签 -->
<header>
  <nav aria-label="主导航">
    <ul>
      <li><a href="/">首页</a></li>
      <li><a href="/docs">文档</a></li>
      <li><a href="/blog">博客</a></li>
    </ul>
  </nav>
</header>

<main>
  <article>
    <h1>文章标题</h1>
    <p>文章内容...</p>
  </article>

  <aside aria-label="相关链接">
    <h2>相关资源</h2>
    <ul>
      <li><a href="#">链接1</a></li>
      <li><a href="#">链接2</a></li>
    </ul>
  </aside>
</main>

<footer>
  <p>&copy; 2024 W3C 标准</p>
</footer>

<!-- 错误:全部使用 div -->
<div class="header">...</div>
<div class="content">...</div>
<div class="sidebar">...</div>
<div class="footer">...</div>

ARIA 属性

当 HTML 语义不足以表达元素用途时,使用 ARIA 属性:

<!-- 标识区域 -->
<div role="navigation" aria-label="主导航">...</div>
<div role="search" aria-label="搜索">...</div>
<div role="dialog" aria-modal="true">...</div>

<!-- 描述状态 -->
<button aria-pressed="true">已选择</button>
<button aria-expanded="false">展开菜单</button>
<div aria-live="polite" aria-atomic="true">
  消息已发送
</div>

<!-- 表单标签 -->
<label for="email">邮箱地址</label>
<input type="email"
       id="email"
       aria-required="true"
       aria-invalid="false">

<!-- 关联错误信息 -->
<input type="text"
       id="username"
       aria-describedby="username-error">
<span id="username-error"
      role="alert"
      class="error">
  用户名长度至少 3 个字符
</span>

键盘导航

确保所有交互都可以通过键盘完成:

<!-- 可聚焦的元素 -->
<button onclick="doSomething()">
  点击按钮
</button>

<a href="/page" role="button">
  链接样式的按钮
</a>

<div role="button"
     tabindex="0"
     onkeydown="handleKey(event)"
     onclick="doSomething()">
  自定义按钮
</div>

<script>
function handleKey(event) {
  if (event.key === 'Enter' || event.key === ' ') {
    doSomething();
    event.preventDefault();
  }
}
</script>
/* 清晰的焦点样式 */
button:focus,
a:focus,
input:focus,
[tabindex]:focus {
  outline: 3px solid #3B82F6;
  outline-offset: 2px;
}

/* 跳过导航链接 */
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #3B82F6;
  color: white;
  padding: 8px;
  text-decoration: none;
}

.skip-link:focus {
  top: 0;
}

图片替代文本

为所有有意义的图片提供替代文本:

<!-- 有意义的图片 -->
<img src="chart.png"
     alt="2024 年销售额增长了 35%"
     width="500"
     height="300">

<!-- 装饰性图片 -->
<img src="decorative.png"
     alt=""
     role="presentation">

<!-- 复杂图片需要详细描述 -->
<img src="complex-chart.png"
     alt="图表显示:1月增长10%,2月增长15%,3月增长20%"
     longdesc="#chart-description">

<div id="chart-description" class="sr-only">
  详细图表描述...
</div>

<!-- 使用 figure 和 figcaption -->
<figure>
  <img src="diagram.png"
       alt="流程图展示用户注册流程">
  <figcaption>
    图 1: 用户注册流程图
  </figcaption>
</figure>

颜色和对比度

确保颜色对比度足够,信息不依赖颜色:

/* WCAG AA 级别:对比度至少 4.5:1 */
.text {
  color: #333; /* 背景白色 */
  background: #fff;
}

/* 更好的对比度 */
.text {
  color: #1a1a1a;
  background: #ffffff;
}

/* 大文本(18pt+)对比度至少 3:1 */
h1 {
  color: #333; /* 24px 字体 */
}

/* 不依赖颜色传递信息 */
.error {
  color: #dc2626;
  font-weight: bold;
  border-left: 4px solid #dc2626;
}

.success {
  color: #16a34a;
  font-weight: bold;
  border-left: 4px solid #16a34a;
}

/* 支持高对比度模式 */
@media (prefers-contrast: high) {
  .button {
    background: #000;
    color: #fff;
    border: 2px solid #fff;
  }
}

表单无障碍

让表单易于填写和理解:

<form aria-label="注册表单">
  <!-- 清晰的标签 -->
  <label for="username">
    用户名
    <span class="required" aria-hidden="true">*</span>
  </label>
  <input type="text"
         id="username"
         name="username"
         required
         aria-required="true"
         aria-describedby="username-help">
  <small id="username-help">3-20 个字符</small>

  <!-- 使用 fieldset 组织相关元素 -->
  <fieldset>
    <legend>性别</legend>
    <label>
      <input type="radio"
             name="gender"
             value="male">
    </label>
    <label>
      <input type="radio"
             name="gender"
             value="female">
    </label>
  </fieldset>

  <!-- 提交按钮 -->
  <button type="submit">注册</button>
</form>

减少动画

尊重用户的偏好设置:

/* 检测用户是否偏好减少动画 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* 平滑滚动 */
html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

测试无障碍

使用工具和真实用户测试无障碍访问:

键盘测试

使用 Tab 键导航,确保所有元素可访问

屏幕阅读器

使用 NVDA、JAWS 或 VoiceOver 测试

自动化工具

使用 axe DevTools、Lighthouse 检测问题

用户测试

邀请残疾用户进行真实测试

使用对比度检查工具验证颜色

WCAG 验证

使用 WAI-ARIA Authoring Practices 验证

总结

无障碍访问是 Web 开发的重要组成部分。通过遵循 WCAG 标准、使用语义化 HTML、添加 ARIA 属性、确保键盘导航,我们可以让 Web 真正为所有人服务。

记住,无障碍访问不仅是道德责任,也是法律要求。许多国家和地区都有相关法律要求网站满足无障碍标准。更重要的是,无障碍访问通常能改善所有用户体验——不仅是有障碍的用户。

让 Web 成为一个包容、开放、人人可访问的数字空间,这是我们每个开发者的责任。