Highlight
Safari 在 2022 年新增了对 HTML dialog 元素的支持、Web Speech API 中的 SSML 支持,以及更完善的 ARIA 自定义控件支持,让开发者可以用更少的代码构建无障碍的 Web 应用。
核心内容
辅助技术概述
全球约七分之一的人有某种形式的残障,影响他们与设备和 Web 的交互方式。Apple 提供了多种辅助工具:VoiceOver(屏幕阅读器)、Switch Control(切换控制)、Voice Control(语音控制)和 Full Keyboard Access(全键盘访问)。
(00:36)
这些工具依赖网页的语义信息来工作。Safari 对语义 HTML 元素(button、h1-h6、table、list 等)有内置的无障碍支持,这是首选方案。但当语义 HTML 无法满足需求时,需要用 JavaScript 创建自定义控件,并补充 ARIA 属性。
自定义控件的无障碍化
Session 用一个披萨切片选择器作为示例。这是一个自定义交互控件:用户点击披萨托盘上的不同位置来选择切片数量。
(02:42)
初始实现只有点击事件,对视觉障碍用户完全不可用。改造步骤:
- 添加
role="slider",让辅助技术知道这个控件是一个滑块 - 添加
tabindex="0",让键盘用户可以聚焦到这个控件 - 添加 ARIA 属性:
aria-valuemin、aria-valuemax、aria-valuenow、aria-valuetext - 处理键盘事件:上下左右箭头键调整数值
这里有一个关键细节:在 iOS 上,VoiceOver 用户 swipe up 增加滑块值、swipe down 减少滑块值时,Safari 会自动模拟对应的箭头键事件。所以只需要监听键盘事件,就能同时支持 VoiceOver 手势和键盘操作。
SSML 语音合成
Web Speech API 的 SpeechSynthesis 接口在 Safari 中新增了对 SSML(Speech Synthesis Markup Language)的支持。
(07:09)
SSML 可以控制语音合成的多个方面:
<break>:插入停顿<phoneme>:控制单词发音<prosody>:调整音高、语速和音量<lang>:指定语言区域
Session 的示例是一个语言学习测验:点击”朗读问题”按钮,英文问题用美语发音,西班牙语选项用墨西哥西班牙语发音,每个选项前有短暂停顿。
HTML Dialog 元素
Safari 新增了对 HTML <dialog> 元素的支持,让模态框的无障碍实现变得简单。
(10:50)
<dialog> 自动处理:
- 焦点管理
- Escape 键关闭
- iOS 的 scrub 手势关闭(快速左右滑动)
- 模态外部的元素对辅助技术不可见
配合 aria-labelledby 和 autofocus 属性,可以进一步优化屏幕阅读器用户的体验。
详细内容
自定义滑块控件
HTML 结构:
<div id="pizza-input"
role="slider" tabindex="0"
aria-valuemin="0" aria-valuemax="8"
aria-valuenow="4" aria-valuetext="4 slices">
</div>
关键点:
role="slider"告诉辅助技术这是一个滑块控件tabindex="0"让键盘和辅助技术可以聚焦到该元素aria-valuemin和aria-valuemax定义取值范围aria-valuenow表示当前数值aria-valuetext提供比数字更友好的描述(“4 slices” 比 “4” 更易懂)
JavaScript 实现:
class PizzaControl {
constructor(id) {
this.control = document.getElementById(id);
this.sliceCount = 4;
// 点击事件:根据点击位置计算切片数量
this.control.addEventListener("click", (event) => {
const newSliceCount = this.computeSliceCount(event);
this.update(newSliceCount);
});
// 键盘事件:支持方向键调整
this.control.addEventListener("keydown", (event) => {
const key = event.key;
if (key === "ArrowRight" || key === "ArrowUp")
this.update(this.sliceCount + 1);
else if (key === "ArrowLeft" || key === "ArrowDown")
this.update(this.sliceCount - 1);
});
}
update(newSliceCount) {
// 限制在 0-8 范围内
this.sliceCount = Math.max(0, Math.min(newSliceCount, 8));
// 视觉更新
this.renderSlices(this.sliceCount);
// 同步更新 ARIA 属性
this.control.setAttribute("aria-valuenow", this.sliceCount);
const sliceModifier = this.sliceCount === 1 ? "slice" : "slices";
this.control.setAttribute("aria-valuetext",
`${this.sliceCount} ${sliceModifier}`);
}
}
关键点:
- 点击事件处理鼠标/触摸交互
- 键盘事件同时服务键盘用户和 VoiceOver 用户
update()方法同时更新视觉表现和 ARIA 属性- 规则:更新视觉时,必须同步更新 ARIA 表示
SSML 语音合成
function wrapWithSSML(phrase, locale) {
return `
<break time="100ms"/>
<prosody rate="80%">
<lang xml:lang="${locale}">
${phrase}
</lang>
</prosody>
`;
}
const readQuestionButton = document.getElementById("read-question-btn");
readQuestionButton.addEventListener("click", () => {
const ssml = `
<speak>
How do you say
${wrapWithSSML("the water", "en-US")}
in Spanish?
${wrapWithSSML("El agua", "es-MX")}
${wrapWithSSML("La abuela", "es-MX")}
${wrapWithSSML("La abeja", "es-MX")}
${wrapWithSSML("El árbol", "es-MX")}
</speak>
`;
const utterance = new SpeechSynthesisUtterance(ssml);
window.speechSynthesis.speak(utterance);
});
关键点:
- 整个 SSML 字符串包裹在
<speak>元素中 <break>插入 100ms 停顿,在选项之间制造节奏<prosody rate="80%">将语速降低到 80%,更容易听清<lang xml:lang="...">切换发音语言区域SpeechSynthesisUtterance接收 SSML 字符串,传给speechSynthesis.speak()播放
HTML 中的按钮:
<button id="read-question-btn">
Read question<span aria-hidden="true">🔊</span>
</button>
关键点:
- 扬声器 emoji 用
aria-hidden="true"隐藏,避免屏幕阅读器读出无意义的 emoji 描述
Dialog 模态框
基础实现:
<dialog id="show-score-modal">
<form method="dialog">
You got all six questions correct. Great work!
<button type="submit">Close</button>
</form>
</dialog>
const showScoreButton = document.getElementById("show-score-btn");
showScoreButton.addEventListener("click", () => {
document.getElementById("show-score-modal").showModal();
});
关键点:
<dialog>元素原生支持模态行为method="dialog"让表单内的提交按钮自动关闭对话框showModal()显示模态框,自动管理焦点和背景遮挡
优化后的无障碍版本:
<dialog id="show-score-modal" aria-labelledby="modal-content">
<form method="dialog">
<span id="modal-content">
You got all six questions correct. Great work!
</span>
<button type="submit" autofocus>Close</button>
</form>
</dialog>
关键点:
aria-labelledby指向内容元素的 ID,让屏幕阅读器打开时先读出内容autofocus将初始焦点放在关闭按钮上,用户可以直接操作- 系统自动处理 Escape 键和 iOS scrub 手势关闭
核心启发
做一个无障碍优先的在线测验平台
- 做什么:一个支持多种题型的在线考试/测验系统,所有交互都对屏幕阅读器友好
- 为什么值得做:自定义控件(如滑块、拖拽排序)在测验中很常见,用 ARIA + 键盘事件可以做出既好看又无障碍的体验
- 怎么开始:用
role、aria-*属性和键盘事件构建自定义控件,用<dialog>显示结果和确认框,用 SSML 朗读题目
做一个语音辅助的烹饪 App
- 做什么:一个 Web 烹饪教程,用户可以用语音控制步骤切换,同时听到带语调的朗读
- 为什么值得做:厨房场景下手是湿的/油腻的,语音交互和清晰的语音反馈很实用
- 怎么开始:用 Web Speech API 的 SpeechRecognition 接收语音指令,用 SSML 控制步骤朗读的语速和停顿
做一个全键盘可操作的图片编辑器
- 做什么:一个轻量级 Web 图片编辑器,所有功能都可以通过键盘完成
- 为什么值得做:很多创意工具只支持鼠标,对运动障碍用户不友好
- 怎么开始:为每个工具设置
tabindex和键盘快捷键,用 ARIA live region 播报操作结果,用<dialog>处理确认弹窗
做一个多语言学习卡片 App
- 做什么:一个语言学习工具,卡片正面是单词,背面是释义和例句,支持语音朗读
- 为什么值得做:SSML 的
<lang>标签让不同语言的单词可以用正确的发音读出,<phoneme>可以处理特殊发音 - 怎么开始:用 SSML 包装每个单词和例句,用
<prosody>调整语速,用aria-live区域播报当前卡片状态
关联 Session
- Support Full Keyboard Access in your iOS app — iOS 全键盘访问支持指南
- What’s new in Safari and WebKit — Safari 和 WebKit 新特性
- Meet Passkeys — Passkeys 无密码认证
评论
GitHub Issues · utterances