可访问性

创建对所有用户都可访问的网页

Web可访问性概述

Web可访问性(Web Accessibility)是指网站、工具和技术能够被残障人士使用的程度。可访问性涵盖了所有影响访问Web内容的障碍,包括视觉、听觉、物理、言语、认知和神经性障碍。

提示: 可访问性不仅帮助残障人士,也帮助有暂时性损伤的人(如手臂骨折)和情境限制的人(如在强光下使用手机)。

可访问性的重要性

  • 道德责任: 确保每个人都能访问信息和服务
  • 法律要求: 许多国家和地区有可访问性相关法律
  • 商业利益: 扩大用户群体,改善用户体验
  • SEO好处: 可访问性实践通常与SEO最佳实践重叠
  • 创新驱动: 可访问性设计往往带来更好的整体设计

可访问性法规和标准

全球范围内有多种可访问性法规和标准,包括:

  • WCAG(Web内容可访问性指南): 国际公认的Web可访问性标准
  • Section 508: 美国联邦机构必须遵循的可访问性标准
  • EN 301 549: 欧洲可访问性标准
  • AODA: 加拿大安大略省的可访问性法案

WCAG指南

Web内容可访问性指南(WCAG)是国际公认的Web可访问性标准:

四项原则(POUR)

原则 描述 关键要点
可感知 信息和用户界面组件必须以用户可以感知的方式呈现 提供文本替代、时间基媒体替代、适应性强、可区分
可操作 用户界面组件和导航必须可操作 键盘可访问、足够的时间、不会引起癫痫、易于导航
可理解 信息和用户界面的操作必须可理解 可读性、可预测性、输入辅助
健壮性 内容必须足够健壮,能够被各种用户代理可靠地解释 兼容性

一致性等级

  • A级: 最基本级别的可访问性
  • AA级: 解决大多数可访问性问题的级别,许多法律要求此级别
  • AAA级: 最高级别的可访问性

WCAG 2.1和2.2新增内容

WCAG 2.1和2.2版本增加了对移动设备、低视力用户和认知障碍用户的支持:

  • 移动可访问性: 手势操作、指针取消、目标尺寸等
  • 认知和学习的可访问性: 识别目的、一致导航等
  • 低视力的可访问性: 文本间距、内容悬停等

ARIA(可访问的富互联网应用程序)

ARIA是一组属性,用于增强HTML的可访问性,特别是在动态内容和复杂UI组件中。

主要ARIA属性

属性 描述 示例
role 定义元素的角色 role="navigation"
aria-label 为元素提供标签 aria-label="关闭菜单"
aria-labelledby 引用其他元素作为标签 aria-labelledby="title1"
aria-describedby 引用其他元素作为描述 aria-describedby="help-text"
aria-hidden 对辅助技术隐藏元素 aria-hidden="true"
aria-expanded 指示可折叠元素的展开状态 aria-expanded="false"
aria-required 指示输入字段是必需的 aria-required="true"
aria-live 指示动态内容区域 aria-live="polite"

ARIA使用示例

ARIA示例
<!-- 导航区域 -->
<nav aria-label="主导航">
    <ul>
        <li><a href="#">首页</a></li>
        <li><a href="#">关于</a></li>
    </ul>
</nav>

<!-- 对话框 -->
<div role="dialog" aria-labelledby="dialog-title" aria-describedby="dialog-desc">
    <h2 id="dialog-title">确认删除</h2>
    <p id="dialog-desc">您确定要删除此项吗?此操作不可撤销。</p>
    <button aria-label="确认删除">删除</button>
    <button aria-label="取消删除">取消</button>
</div>

<!-- 进度指示器 -->
<div role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">
    75%
</div>

<!-- 动态内容区域 -->
<div aria-live="polite" aria-atomic="true">
    动态更新的内容将在这里宣布
</div>
注意: 只有在必要时才使用ARIA。首先使用原生HTML元素,因为它们已经内置了可访问性特性。

ARIA角色分类

ARIA角色分为以下几类:

  • 抽象角色: 不应直接使用的角色,仅用于角色继承
  • 小部件角色: 用于交互式UI元素的角色,如按钮、滑块等
  • 文档结构角色: 描述页面结构的角色,如文章、横幅等
  • 地标角色: 标识页面重要区域的角色,如导航、主要等

键盘可访问性

确保所有功能都可以通过键盘访问:

Tab键导航

  • 确保所有交互元素(链接、按钮、表单控件)可以通过Tab键访问
  • 使用tabindex属性控制Tab键顺序
  • 避免使用tabindex值大于0
  • 使用tabindex="-1"从Tab键顺序中移除元素,但仍允许编程聚焦

键盘操作

键盘操作示例
<!-- 可折叠内容 -->
<button aria-expanded="false" aria-controls="collapsible-content">
    显示更多
</button>
<div id="collapsible-content" hidden>
    可折叠的内容...
</div>

<script>
document.querySelector('button').addEventListener('click', function() {
    const expanded = this.getAttribute('aria-expanded') === 'true';
    this.setAttribute('aria-expanded', !expanded);
    document.getElementById('collapsible-content').hidden = expanded;
});

// 添加键盘支持
document.querySelector('button').addEventListener('keydown', function(e) {
    if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        this.click();
    }
});
</script>

跳过链接

跳过链接
<!-- 跳过导航链接 -->
<a href="#main-content" class="skip-link">跳到主内容</a>

<header>
    <!-- 导航菜单 -->
</header>

<main id="main-content" tabindex="-1">
    <!-- 主内容 -->
</main>

<style>
.skip-link {
    position: absolute;
    top: -40px;
    left: 0;
    background: #000;
    color: #fff;
    padding: 8px;
    z-index: 100;
}

.skip-link:focus {
    top: 0;
}
</style>

键盘快捷键

为复杂交互提供键盘快捷键:

  • Enter/空格键: 激活按钮或链接
  • Tab键: 在可聚焦元素间移动
  • 箭头键: 在选项间导航
  • Esc键: 关闭对话框或弹出窗口

图像和多媒体可访问性

图像替代文本

alt文本最佳实践
<!-- 信息性图像 -->
<img src="chart.png" alt="2026年销售图表,显示第一季度增长20%">

<!-- 装饰性图像 -->
<img src="divider.png" alt="">

<!-- 链接中的图像 -->
<a href="/about">
    <img src="logo.png" alt="公司首页 - 关于我们">
</a>

<!-- 复杂图像的长描述 -->
<img src="infographic.png" alt="气候变化影响信息图" longdesc="climate-desc.html">
<a href="climate-desc.html">信息图详细描述</a>

多媒体可访问性

音频和视频可访问性
<!-- 视频字幕和描述 -->
<video controls>
    <source src="video.mp4" type="video/mp4">
    <track kind="captions" src="captions.vtt" srclang="zh" label="中文">
    <track kind="descriptions" src="descriptions.vtt" srclang="zh">
</video>

<!-- 音频转录 -->
<audio controls>
    <source src="speech.mp3" type="audio/mpeg">
</audio>
<a href="transcript.html">查看文字转录</a>

SVG可访问性

确保SVG图形的可访问性:

SVG可访问性
<svg role="img" aria-labelledby="svg-title svg-desc">
    <title id="svg-title">公司增长图表</title>
    <desc id="svg-desc">
        显示过去五年公司收入稳步增长的折线图,
        从2022年的100万元增长到2026年的500万元
    </desc>
    <!-- SVG内容 -->
</svg>

表单可访问性

标签和指令

表单标签
<!-- 显式标签 -->
<label for="username">用户名:</label>
<input type="text" id="username" name="username">

<!-- 隐式标签 -->
<label>
    邮箱:
    <input type="email" name="email">
</label>

<!-- 使用aria-labelledby -->
<span id="phone-label">电话号码:</span>
<input type="tel" aria-labelledby="phone-label">

<!-- 使用aria-describedby提供额外信息 -->
<label for="password">密码:</label>
<input type="password" id="password" name="password" 
       aria-describedby="password-help">
<div id="password-help">密码必须包含至少8个字符</div>

错误处理

表单错误
<form>
    <label for="email">邮箱:</label>
    <input type="email" id="email" name="email" 
           aria-invalid="false"
           aria-describedby="email-error">
    <div id="email-error" role="alert"></div>
    
    <button type="submit">提交</button>
</form>

<script>
document.querySelector('form').addEventListener('submit', function(e) {
    e.preventDefault();
    const email = document.getElementById('email');
    const error = document.getElementById('email-error');
    
    if (!email.value.includes('@')) {
        email.setAttribute('aria-invalid', 'true');
        error.textContent = '请输入有效的邮箱地址';
        email.focus();
    } else {
        email.setAttribute('aria-invalid', 'false');
        error.textContent = '';
        // 提交表单
    }
});
</script>

表单分组

使用fieldsetlegend对相关表单控件进行分组:

表单分组
<fieldset>
    <legend>联系信息</legend>
    
    <label for="name">姓名:</label>
    <input type="text" id="name" name="name">
    
    <label for="email">邮箱:</label>
    <input type="email" id="email" name="email">
</fieldset>

<fieldset>
    <legend>订阅偏好</legend>
    
    <input type="checkbox" id="newsletter" name="newsletter">
    <label for="newsletter">订阅新闻通讯</label>
    
    <input type="checkbox" id="promotions" name="promotions">
    <label for="promotions">接收促销信息</label>
</fieldset>

颜色和对比度

颜色对比度

  • 文本和背景之间应有足够的对比度
  • 普通文本的对比度至少为4.5:1
  • 大文本(18pt以上或14pt粗体)的对比度至少为3:1
  • 使用工具检查对比度,如WebAIM颜色对比度检查器

不依赖颜色传达信息

颜色使用
<!-- 不推荐:仅用颜色表示状态 -->
<span style="color: red;">错误</span>

<!-- 推荐:使用颜色和文本 -->
<span style="color: red;" aria-label="错误">
    <span class="sr-only">错误:</span>
    请输入有效的邮箱地址
</span>

<!-- 推荐:使用图标和文本 -->
<span>
    <svg aria-hidden="true">...</svg>
    <span class="sr-only">错误:</span>
    请输入有效的邮箱地址
</span>

颜色盲友好设计

确保设计对色盲用户友好:

  • 避免仅使用颜色区分重要信息
  • 使用图案、纹理或文字标签作为补充
  • 测试设计在不同类型色盲下的表现

测试和工具

自动化测试工具

  • WAVE: Web可访问性评估工具
  • axe: 可访问性测试引擎
  • Lighthouse: Chrome开发者工具中的可访问性审计
  • HTML CodeSniffer: HTML可访问性检查器

手动测试

  • 仅使用键盘导航整个网站
  • 使用屏幕阅读器测试(如NVDA、JAWS、VoiceOver)
  • 检查颜色对比度
  • 验证语义结构
  • 测试在不同缩放级别下的可用性

屏幕阅读器测试

屏幕阅读器兼容性
<!-- 测试页面结构 -->
<h1>页面主标题</h1>
<nav aria-label="主导航">...</nav>
<main>
    <h2>主要内容标题</h2>
    <!-- 内容 -->
</main>

<!-- 测试图像替代文本 -->
<img src="logo.png" alt="公司logo">

<!-- 测试表单可访问性 -->
<label for="search">搜索:</label>
<input type="search" id="search" name="search">
<button>搜索</button>

用户测试

与残障用户一起测试网站:

  • 招募不同残障类型的用户参与测试
  • 观察他们如何使用网站
  • 收集反馈并改进设计

移动设备可访问性

触摸目标尺寸

确保触摸目标足够大,便于操作:

  • 最小触摸目标尺寸为44x44像素
  • 触摸目标间有足够的间距
  • 避免触摸目标过小或过于接近

手势操作

确保手势操作可访问:

  • 提供替代的单指操作
  • 避免复杂的手势操作
  • 提供取消手势操作的方法

响应式设计的可访问性

确保响应式设计对所有用户都可访问:

  • 在小屏幕上保持足够的对比度
  • 确保文本在小屏幕上可读
  • 测试在各种设备尺寸下的可访问性

可访问性声明

创建可访问性声明,告知用户网站的可访问性状态:

声明内容

  • 网站的可访问性标准遵循情况
  • 已知的可访问性问题
  • 反馈和联系信息
  • 声明的创建和更新日期

声明示例

可访问性声明
<section aria-labelledby="accessibility-statement">
    <h2 id="accessibility-statement">可访问性声明</h2>
    <p>我们致力于确保所有用户都能访问我们的网站。</p>
    
    <h3>遵循标准</h3>
    <p>本网站遵循WCAG 2.1 AA级标准。</p>
    
    <h3>已知问题</h3>
    <ul>
        <li>某些PDF文档可能不完全可访问</li>
        <li>部分视频缺少字幕</li>
    </ul>
    
    <h3>反馈</h3>
    <p>如果您遇到任何可访问性问题,请<a href="contact.html">联系我们</a>。</p>
</section>

动手练习

创建一个可访问的网页组件:

综合练习
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>可访问性实践 - 标签页组件</title>
    <style>
        .tabs {
            margin: 20px 0;
        }
        .tab-list {
            display: flex;
            list-style: none;
            padding: 0;
            margin: 0;
            border-bottom: 1px solid #ccc;
        }
        .tab {
            padding: 10px 20px;
            border: 1px solid transparent;
            border-bottom: none;
            background: #f5f5f5;
            cursor: pointer;
            margin-right: 5px;
            border-radius: 5px 5px 0 0;
        }
        .tab:focus {
            outline: 2px solid #007bff;
            outline-offset: 2px;
        }
        .tab[aria-selected="true"] {
            background: #fff;
            border-color: #ccc;
            border-bottom-color: #fff;
            margin-bottom: -1px;
        }
        .tab-panel {
            padding: 20px;
            border: 1px solid #ccc;
            border-top: none;
            background: #fff;
        }
        .tab-panel:focus {
            outline: 2px solid #007bff;
            outline-offset: 2px;
        }
        .sr-only {
            position: absolute;
            width: 1px;
            height: 1px;
            padding: 0;
            margin: -1px;
            overflow: hidden;
            clip: rect(0, 0, 0, 0);
            white-space: nowrap;
            border: 0;
        }
    </style>
</head>
<body>
    <h1>可访问的标签页组件</h1>
    
    <div class="tabs">
        <div class="tablist" role="tablist" aria-label="示例标签页">
            <button class="tab" 
                    role="tab" 
                    aria-selected="true" 
                    aria-controls="panel-1" 
                    id="tab-1">
                标签一
            </button>
            <button class="tab" 
                    role="tab" 
                    aria-selected="false" 
                    aria-controls="panel-2" 
                    id="tab-2" 
                    tabindex="-1">
                标签二
            </button>
            <button class="tab" 
                    role="tab" 
                    aria-selected="false" 
                    aria-controls="panel-3" 
                    id="tab-3" 
                    tabindex="-1">
                标签三
            </button>
        </div>
        
        <div class="tab-panel" 
             role="tabpanel" 
             id="panel-1" 
             aria-labelledby="tab-1" 
             tabindex="0">
            <h2>标签一内容</h2>
            <p>这是第一个标签页的内容。</p>
        </div>
        
        <div class="tab-panel" 
             role="tabpanel" 
             id="panel-2" 
             aria-labelledby="tab-2" 
             tabindex="0" 
             hidden>
            <h2>标签二内容</h2>
            <p>这是第二个标签页的内容。</p>
        </div>
        
        <div class="tab-panel" 
             role="tabpanel" 
             id="panel-3" 
             aria-labelledby="tab-3" 
             tabindex="0" 
             hidden>
            <h2>标签三内容</h2>
            <p>这是第三个标签页的内容。</p>
        </div>
    </div>
    
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const tabs = document.querySelectorAll('.tab');
            const panels = document.querySelectorAll('.tab-panel');
            
            tabs.forEach(tab => {
                tab.addEventListener('click', function() {
                    // 取消所有标签的选中状态
                    tabs.forEach(t => {
                        t.setAttribute('aria-selected', 'false');
                        t.setAttribute('tabindex', '-1');
                    });
                    
                    // 隐藏所有面板
                    panels.forEach(panel => {
                        panel.hidden = true;
                    });
                    
                    // 激活当前标签
                    this.setAttribute('aria-selected', 'true');
                    this.removeAttribute('tabindex');
                    
                    // 显示对应面板
                    const panelId = this.getAttribute('aria-controls');
                    document.getElementById(panelId).hidden = false;
                    document.getElementById(panelId).focus();
                });
                
                // 键盘导航
                tab.addEventListener('keydown', function(e) {
                    const key = e.key;
                    const currentIndex = Array.from(tabs).indexOf(this);
                    
                    if (key === 'ArrowRight' || key === 'ArrowLeft') {
                        // 阻止默认滚动行为
                        e.preventDefault();
                        
                        let nextIndex;
                        if (key === 'ArrowRight') {
                            nextIndex = (currentIndex + 1) % tabs.length;
                        } else {
                            nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
                        }
                        
                        tabs[nextIndex].click();
                        tabs[nextIndex].focus();
                    }
                    
                    if (key === 'Home') {
                        e.preventDefault();
                        tabs[0].click();
                        tabs[0].focus();
                    }
                    
                    if (key === 'End') {
                        e.preventDefault();
                        tabs[tabs.length - 1].click();
                        tabs[tabs.length - 1].focus();
                    }
                });
            });
        });
    </script>
</body>
</html>

下一步学习

掌握可访问性后,继续学习:

1

高级HTML

学习HTML5高级特性和最佳实践

继续学习
2

HTML参考手册

查阅完整的HTML标签和属性参考

继续学习