可访问性合规Skill accessibility-compliance

这个技能提供新闻网站、学术平台等的可访问性合规模式,用于构建无障碍界面、审计网站的WCAG合规性、编写图像替代文本、创建可访问数据可视化、确保多媒体内容可访问,并满足法律要求。它帮助开发人员和内容发布者确保所有读者(包括使用辅助技术的用户)都能平等访问网络内容。关键词:可访问性、无障碍设计、WCAG、新闻网站、数据可视化、图像替代文本、键盘导航、屏幕阅读器。

前端开发 0 次安装 0 次浏览 更新于 3/15/2026

名称:accessibility-compliance 描述:用于新闻网站、新闻工具和学术平台的Web可访问性模式。当构建无障碍界面、审计现有网站的WCAG合规性、为新闻图像编写替代文本、创建可访问数据可视化或确保内容到达所有读者(包括使用辅助技术的读者)时使用。对于新闻室开发人员和任何发布网络内容的人员来说至关重要。

可访问性合规

新闻和学术网络发布的实用可访问性模式。

何时激活

  • 构建或审计新闻网站
  • 为文章图像编写替代文本
  • 创建可访问数据可视化
  • 开发新闻记者使用的工具
  • 确保多媒体内容可访问
  • 满足法律可访问性要求
  • 在线发布学术内容

新闻网站的WCAG要点

四大原则(POUR)

## WCAG 2.1 AA 检查清单(新闻焦点)

### 可感知
- [ ] 图像有有意义的替代文本
- [ ] 视频有字幕
- [ ] 音频有转录稿
- [ ] 颜色不是唯一传达信息的方式
- [ ] 文本可以调整到200%而不破坏

### 可操作
- [ ] 所有功能仅用键盘即可工作
- [ ] 无键盘陷阱
- [ ] 跳过链接到主要内容
- [ ] 页面标题描述内容
- [ ] 所有交互元素焦点可见

### 可理解
- [ ] 在HTML中声明语言
- [ ] 导航一致
- [ ] 错误消息清晰
- [ ] 标签描述表单字段

### 健壮
- [ ] 有效的HTML
- [ ] ARIA正确使用(或根本不使用)
- [ ] 与屏幕阅读器兼容
- [ ] 缩放/文本调整大小时不破坏

图像可访问性

新闻的替代文本

## 替代文本决策树

### 新闻照片
- **谁** 在图像中(如果可识别且相关)
- **什么** 正在发生(动作或情况)
- **哪里**(如果位置对故事重要)
- **不要**:逐字重复标题文本

### 示例

照片:法院外抗议者举着标语

差:"抗议者"
差:"抗议图像"(冗余的"图像")
好:"约50名抗议者在西雅图市中心的联邦法院外举着标语,上面写着'立即正义'"

照片:采访对象的头部照片

差:"照片"
好:"约翰·霍普金斯大学的流行病学家Sarah Chen博士"

照片:嵌入为图像的图表

差:"显示数据的图表"
好:"条形图显示失业率从2020年3月的3.5%上升到6月的8.2%。完整数据在下表。"

替代文本Python助手

def generate_alt_text_prompt(context: dict) -> str:
    """为AI替代文本协助生成提示。"""
    return f"""
    为新闻图像编写替代文本。

    故事背景:{context.get('headline', '未知')}
    图像类型:{context.get('image_type', '照片')}
    标题(如果有):{context.get('caption', '无')}

    指南:
    - 简洁(如果可能,少于125个字符)
    - 不要以"图像"或"照片"开头
    - 包含故事背景的相关细节
    - 不要完全复制标题
    - 描述视觉上重要的内容

    如果这是纯装饰性的,回应:""
    """

def is_decorative(image_context: str) -> bool:
    """检查图像是否纯装饰性(空替代文本合适)。"""
    decorative_indicators = [
        'decorative',
        'separator',
        'background',
        'spacer',
        'border'
    ]
    return any(ind in image_context.lower() for ind in decorative_indicators)

可访问数据可视化

图表可访问性检查清单

## 使图表可访问

### 基本元素
- [ ] 描述关键洞察的文本替代
- [ ] 数据表可用(可见或链接)
- [ ] 颜色有足够对比度
- [ ] 模式/纹理补充颜色编码
- [ ] 标签直接在图表上(不仅仅图例)
- [ ] 标题描述图表显示的内容

### 交互式图表
- [ ] 键盘可导航
- [ ] 焦点指示器可见
- [ ] 屏幕阅读器宣布数据点
- [ ] 工具提示可通过键盘访问
- [ ] 缩放不破坏布局

可访问图表组件

<!-- 可访问图表模式 -->
<figure role="figure" aria-labelledby="chart-title" aria-describedby="chart-desc">
  <figcaption>
    <h3 id="chart-title">失业率 2020-2024</h3>
    <p id="chart-desc">
      折线图显示失业率从2020年1月的3.5%开始,
      在2020年4月飙升到14.7%,并逐渐下降到2024年的3.9%。
    </p>
  </figcaption>

  <!-- 图表本身 -->
  <div id="chart" role="img" aria-label="交互式折线图。数据表在下方可用。">
    <!-- 图表在此渲染 -->
  </div>

  <!-- 始终提供数据表 -->
  <details>
    <summary>查看数据表</summary>
    <table>
      <caption>月度失业率数据</caption>
      <thead>
        <tr>
          <th scope="col">月份</th>
          <th scope="col">失业率(%)</th>
        </tr>
      </thead>
      <tbody>
        <tr><td>2020年1月</td><td>3.5</td></tr>
        <tr><td>2020年4月</td><td>14.7</td></tr>
        <!-- 等等 -->
      </tbody>
    </table>
  </details>
</figure>

色盲安全调色板

# 用于数据可视化的安全颜色调色板
COLOR_PALETTES = {
    # Paul Tol的颜色方案 - 广泛测试用于可访问性
    'bright': [
        '#4477AA',  # 蓝色
        '#EE6677',  # 红色
        '#228833',  # 绿色
        '#CCBB44',  # 黄色
        '#66CCEE',  # 青色
        '#AA3377',  # 紫色
        '#BBBBBB',  # 灰色
    ],

    # 分类(对大多数色盲安全)
    'categorical': [
        '#332288',  # 靛蓝色
        '#88CCEE',  # 青色
        '#44AA99',  # 蓝绿色
        '#117733',  # 绿色
        '#999933',  # 橄榄色
        '#DDCC77',  # 沙色
        '#CC6677',  # 玫瑰色
        '#882255',  # 酒红色
    ],

    # 顺序(单色调)
    'sequential_blue': [
        '#f7fbff',
        '#deebf7',
        '#c6dbef',
        '#9ecae1',
        '#6baed6',
        '#4292c6',
        '#2171b5',
        '#084594',
    ],

    # 发散(用于有意义的中间点)
    'diverging': [
        '#d73027',  # 红色(负)
        '#f46d43',
        '#fdae61',
        '#fee08b',
        '#ffffbf',  # 中性
        '#d9ef8b',
        '#a6d96a',
        '#66bd63',
        '#1a9850',  # 绿色(正)
    ]
}

def validate_contrast(color1: str, color2: str) -> float:
    """计算两种颜色之间的WCAG对比度比率。"""
    def hex_to_rgb(hex_color):
        hex_color = hex_color.lstrip('#')
        return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))

    def relative_luminance(rgb):
        r, g, b = [x / 255.0 for x in rgb]
        r = r / 12.92 if r <= 0.03928 else ((r + 0.055) / 1.055) ** 2.4
        g = g / 12.92 if g <= 0.03928 else ((g + 0.055) / 1.055) ** 2.4
        b = b / 12.92 if b <= 0.03928 else ((b + 0.055) / 1.055) ** 2.4
        return 0.2126 * r + 0.7152 * g + 0.0722 * b

    l1 = relative_luminance(hex_to_rgb(color1))
    l2 = relative_luminance(hex_to_rgb(color2))

    lighter = max(l1, l2)
    darker = min(l1, l2)

    return (lighter + 0.05) / (darker + 0.05)

# WCAG要求:
# 正常文本:4.5:1 最低(AA),7:1 增强(AAA)
# 大文本(18点以上):3:1 最低(AA),4.5:1 增强(AAA)
# UI组件:3:1 最低

视频和音频可访问性

字幕要求

## 视频字幕检查清单

### 质量标准
- [ ] 99%+ 准确性
- [ ] 与音频同步(1秒内)
- [ ] 多个说话者的说话者识别
- [ ] 音效描述 [掌声] [音乐]
- [ ] 非语音音频相关时描述

### 技术要求
- [ ] 字幕在播放器控件中可用
- [ ] 可以切换开启/关闭
- [ ] 样式不重叠视频内容
- [ ] 可读的字体大小和对比度

### 字幕格式(SRT示例)
1
00:00:01,000 --> 00:00:04,500
[新闻主播] 晚上好。今晚突发新闻
从市中心,抗议者聚集在那里。

2
00:00:04,600 --> 00:00:08,200
我们正在直播现场记者Jane Smith
在场景。Jane?

视频的音频描述

## 何时需要音频描述

### 必需
- [ ] 关键视觉信息不在对话中
- [ ] 对理解故事至关重要的动作
- [ ] 屏幕上的文本未被读出
- [ ] 说话者的识别信息

### 示例脚本

原始视频:[记者站在燃烧的建筑前]
对话:"火灾大约从凌晨3点开始..."

音频描述版本:
[一位穿红色夹克的记者站在一栋五层
公寓楼前,被火焰吞噬。消防车
可见于背景]
对话:"火灾大约从凌晨3点开始..."

键盘可访问性

焦点管理模式

// 跳过链接实现
document.addEventListener('DOMContentLoaded', () => {
  const skipLink = document.querySelector('.skip-link');
  const mainContent = document.querySelector('main');

  skipLink.addEventListener('click', (e) => {
    e.preventDefault();
    mainContent.setAttribute('tabindex', '-1');
    mainContent.focus();
  });
});

// 模态窗口中的键盘陷阱预防
function createAccessibleModal(modalElement) {
  const focusableElements = modalElement.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  const firstFocusable = focusableElements[0];
  const lastFocusable = focusableElements[focusableElements.length - 1];

  modalElement.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === firstFocusable) {
        e.preventDefault();
        lastFocusable.focus();
      } else if (!e.shiftKey && document.activeElement === lastFocusable) {
        e.preventDefault();
        firstFocusable.focus();
      }
    }

    if (e.key === 'Escape') {
      closeModal();
    }
  });
}

// 焦点指示器样式(永远不要在没有替代的情况下移除轮廓)
/*
:focus {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

:focus:not(:focus-visible) {
  outline: none;  // 为鼠标用户移除
}

:focus-visible {
  outline: 2px solid #005fcc;  // 为键盘用户保留
  outline-offset: 2px;
}
*/

表单和错误处理

可访问表单模式

<!-- 可访问表单字段 -->
<div class="form-group">
  <label for="email">
    电子邮件地址
    <span class="required" aria-hidden="true">*</span>
    <span class="visually-hidden">(必需)</span>
  </label>
  <input
    type="email"
    id="email"
    name="email"
    required
    aria-describedby="email-hint email-error"
    aria-invalid="false"
  >
  <p id="email-hint" class="hint">
    我们将用此向您发送新闻通讯。
  </p>
  <p id="email-error" class="error" role="alert" hidden>
    请输入有效的电子邮件地址。
  </p>
</div>

<style>
  /* 视觉隐藏但对屏幕阅读器可访问 */
  .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
</style>

错误消息模式

function showError(inputElement, message) {
  const errorElement = document.getElementById(
    inputElement.getAttribute('aria-describedby').split(' ').find(id =>
      id.includes('error')
    )
  );

  inputElement.setAttribute('aria-invalid', 'true');
  errorElement.textContent = message;
  errorElement.hidden = false;

  // 向屏幕阅读器宣布错误
  errorElement.setAttribute('role', 'alert');
}

function clearError(inputElement) {
  const errorId = inputElement.getAttribute('aria-describedby')
    .split(' ')
    .find(id => id.includes('error'));
  const errorElement = document.getElementById(errorId);

  inputElement.setAttribute('aria-invalid', 'false');
  errorElement.hidden = true;
  errorElement.removeAttribute('role');
}

测试工具

自动化测试

# 使用axe-core进行可访问性审计(通过Playwright)
from playwright.sync_api import sync_playwright

def run_accessibility_audit(url: str) -> dict:
    """运行自动化可访问性测试。"""
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        page.goto(url)

        # 注入axe-core
        page.add_script_tag(
            url='https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.7.2/axe.min.js'
        )

        # 运行审计
        results = page.evaluate('''
            async () => {
                return await axe.run();
            }
        ''')

        browser.close()

        return {
            'violations': results['violations'],
            'passes': len(results['passes']),
            'incomplete': results['incomplete'],
            'url': url
        }

def format_violations(results: dict) -> str:
    """格式化违规以供审查。"""
    output = []
    for v in results['violations']:
        output.append(f"
## {v['id']}: {v['description']}")
        output.append(f"影响:{v['impact']}")
        output.append(f"WCAG: {', '.join(v.get('tags', []))}")
        for node in v['nodes'][:3]:  # 前3个示例
            output.append(f"  - {node['html'][:100]}")
    return '
'.join(output)

手动测试检查清单

## 手动可访问性测试

### 键盘导航
- [ ] 在整个页面上按Tab键
- [ ] 可以到达所有交互元素
- [ ] 焦点顺序合理
- [ ] 无键盘陷阱
- [ ] 跳过链接有效

### 屏幕阅读器测试
- [ ] 标题以逻辑顺序宣布
- [ ] 图像有有意义的描述
- [ ] 表单标签正确宣布
- [ ] 错误消息被宣布
- [ ] 动态内容更新被宣布

### 缩放测试
- [ ] 200%缩放,无水平滚动
- [ ] 400%缩放,内容仍可用
- [ ] 文本间距调整不破坏布局

### 颜色和对比度
- [ ] 在灰度下工作
- [ ] 链接与文本可区分
- [ ] 错误状态不是仅颜色
- [ ] 对比度检查器通过(4.5:1最低)

法律要求

## 可访问性法律摘要

### 美国
- **第508条**:联邦机构必须可访问
- **ADA**:越来越多地应用于网站
- **州法律**:许多州有额外要求

### 欧盟
- **欧洲可访问性法案**:从2025年起
- **EN 301 549**:技术标准

### 最佳实践
WCAG 2.1 AA 是全球标准。满足此标准,您很可能满足大多数法律要求。

相关技能

  • zero-build-frontend - 构建可访问的静态站点
  • data-journalism - 创建可访问的可视化
  • web-scraping - 确保抓取的内容保留可访问性

技能元数据

字段
版本 1.0.0
创建时间 2025-12-26
作者 Claude Skills for Journalism
领域 开发, 发布
复杂度 中等