图片只有alt文本就够了吗?在中国网络无障碍指南的技术要求里,非文本内容并不仅仅指<img>标签,它覆盖图像、音频、视频、验证码、装饰性图形等多个类别,每一类对替代文本、音频描述、字幕或空值处理都有不同规定。如果IWAC的检测规则直接用散落的字符串和布尔值表示,扩展一个媒体类型就可能同时改动类型判断、校验函数和报告输出。用TypeScript封装非文本内容类型,正是要把这些分散的约束收拢到可编译、可推导的模型里。下面从标准分类、类型定义和IWAC接入三个层面展开。

一、先理清中国网络无障碍指南对非文本内容的分类
非文本内容在无障碍检测中并不是一个单一对象。按照中国网络无障碍指南的通常要求,它至少可以拆成图像、音频、视频、验证码和装饰性元素五类。图像又可以分为承载信息的图片、纯装饰图片、文字图片以及图表等复杂图像。音频内容需要提供完整的文字稿,视频内容则同时涉及字幕、音频描述和手语翻译。验证码比较特殊,它本质上是故意增加机器识别难度的非文本内容,因此不能简单要求提供alt文本,而是必须提供音频验证码、逻辑问答或人工客服等替代访问方式。
这样区分的原因在于,不同类型的非文本内容如果共用同一套检测字段,很容易出现规则冲突。比如装饰性图片按照无障碍实践应当使用空alt并设置aria-hidden,而有意义的图片必须有非空alt;验证码如果也要求写alt,反而会暴露验证码内容,削弱安全作用。正因为这些差异,在IWAC中封装非文本内容类型时,第一步不是急着写interface,而是把标准要求映射成互斥的类型类别。
二、用TypeScript类型系统建模非文本内容
建模时可以先定义一个枚举,把所有非文本类别作为可辨识字段。枚举比字符串字面量更利于重构,因为改一处枚举名,编译器会提示所有使用位置。接着定义一个基础接口,承载id、src、title这类公共字段,不同媒体类型的特有字段再通过扩展接口描述。
export enum NonTextKind {
Image = 'image',
Audio = 'audio',
Video = 'video',
Captcha = 'captcha',
Decorative = 'decorative'
}
export interface AltTextRule {
required: boolean;
minLength?: number;
allowEmptyForDecorative: boolean;
}
export interface BaseNonTextContent {
id: string;
kind: NonTextKind;
src?: string;
title?: string;
rules: AltTextRule[];
}
如果只用一个宽泛的interface,把所有可能字段都写成可选属性,类型检查能力会非常弱。比如音频对象漏掉transcriptUrl,TypeScript并不会报错,因为字段本来就是可选的。更好的做法是使用可辨识联合,让kind字段决定当前对象到底应该拥有哪些必填属性。
export interface ImageContent extends BaseNonTextContent {
kind: NonTextKind.Image;
alt: string;
longDescription?: string;
}
export interface AudioContent extends BaseNonTextContent {
kind: NonTextKind.Audio;
transcriptUrl: string;
hasAudioDescription: boolean;
}
export interface VideoContent extends BaseNonTextContent {
kind: NonTextKind.Video;
captionsUrl: string;
audioDescriptionUrl?: string;
hasSignLanguage: boolean;
}
export interface CaptchaContent extends BaseNonTextContent {
kind: NonTextKind.Captcha;
alternativeMethods: string[];
}
export interface DecorativeContent extends BaseNonTextContent {
kind: NonTextKind.Decorative;
alt: '';
ariaHidden: true;
}
export type NonTextContent =
| ImageContent
| AudioContent
| VideoContent
| CaptchaContent
| DecorativeContent;
这种写法让NonTextContent成为一个可辨识联合。当检测函数拿到一个NonTextContent对象并判断content.kind后,TypeScript会自动收窄类型。例如进入case NonTextKind.Audio分支后,content.transcriptUrl会被识别为必填的字符串,不会出现可能为undefined的误判。更重要的是,新增一个媒体类型时,如果漏写对应的检测分支,TypeScript可以通过switch的穷尽检查给出提示,前提是函数返回类型明确且开启了严格模式。
三、在IWAC中接入类型封装并实现校验
类型封装完成后,IWAC的检测规则就可以直接依赖这些类型签名。例如可以定义一个校验函数,接收NonTextContent联合类型,返回统一的校验结果结构。这样每一个检测分支都被编译器约束,不能随意访问不存在的字段。
type ValidationResult = {
valid: boolean;
errors: string[];
};
export function validateNonTextContent(content: NonTextContent): ValidationResult {
const errors: string[] = [];
switch (content.kind) {
case NonTextKind.Image:
if (!content.alt.trim()) {
errors.push('有意义的图片必须提供非空alt文本');
}
break;
case NonTextKind.Audio:
if (!content.transcriptUrl) {
errors.push('音频内容必须提供文字稿地址');
}
break;
case NonTextKind.Video:
if (!content.captionsUrl) {
errors.push('视频内容必须提供字幕地址');
}
break;
case NonTextKind.Captcha:
if (content.alternativeMethods.length === 0) {
errors.push('验证码必须提供至少一种替代访问方式');
}
break;
case NonTextKind.Decorative:
if (content.alt !== '' || !content.ariaHidden) {
errors.push('装饰性内容应使用空alt并设置aria-hidden');
}
break;
}
return {
valid: errors.length === 0,
errors
};
}
接入IWAC时,通常需要把从页面DOM扫描到的节点转换成对应类型。例如扫描到<img>节点时,先判断它是否属于装饰性图片。如果节点带有role="presentation"或alt="",则构造DecorativeContent;如果它出现在链接或按钮中且有实际语义,则构造ImageContent并填写alt。对于<audio>和<video>节点,需要检查是否存在字幕轨道、文字稿链接或音频描述开关。这些转换逻辑放在独立工厂函数中,比散落在检测脚本里更清晰。
一个常见的错误是把验证码当作普通图片处理。如果IWAC对验证码也要求非空alt,那么语音读屏软件可能会直接读出验证码文字,导致验证机制形同虚设。封装后的类型系统能迫使开发者显式处理验证码分支,至少必须提供替代访问方式数组。这种方式不是靠注释或文档提醒,而是靠TypeScript类型签名强制执行。
四、维护与扩展:让类型约束跟上标准变化
无障碍标准会随着技术发展调整。比如视频直播场景可能增加实时字幕要求,音频内容可能增加手语翻译。此时只需要在联合类型中新增一个分支,或者在VideoContent中增加可选的实时字幕字段,编译器会自动标出所有需要更新的检测分支和工厂函数。相比用any类型或宽泛的Record<string, unknown>,这种强类型模型能大幅降低回归风险。
在实际项目中,建议把这一套类型定义放到单独的包或模块中,与IWAC的检测引擎解耦。类型模块只负责描述非文本内容的结构和规则,检测引擎负责收集节点并调用校验函数。这样即使检测引擎需要支持新的扫描来源,例如PDF、移动端原生界面,类型定义仍然可以复用。
归根结底,用TypeScript封装中国网络无障碍指南中的非文本内容类型,不是为了把标准条文机械地搬进代码,而是为了让无障碍规则的复杂差异在编译期就得到控制。对于IWAC这类需要持续扩展检测能力的工具,强类型带来的不是约束,而是后续维护时更低的沟通成本和更高的规则一致性。
TypeScript网络无障碍非文本内容修改时间:2026-09-23 04:26:45