名称: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 |
| 领域 | 开发, 发布 |
| 复杂度 | 中等 |