关于 touch-pointer
用于设计和审查界面触摸及指针交互的无障碍技能。内容涵盖手势和拖动的单指针替代方式、目标尺寸、取消操作,以及保留浏览器缩放和平移。
主要功能
- 01手势与拖动的单指针替代方式
- 02指针操作取消指南
- 03交互目标最小尺寸要求
- 04自定义交互使用 Pointer Events
- 05缩放、平移与悬停保护
适用场景
- 01审查拖放界面
- 02为地图或轮播添加非手势控件
- 03检查触摸目标尺寸和指针取消行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mgifford/ accessibility-skills --skill touch-pointer安装后在 Claude Code 中输入/touch-pointer确认已加载。
附带文件README.mdSYNC.md
详细介绍
触摸和指针无障碍技能
权威来源:
mgifford/ACCESSIBILITY.md中的examples/TOUCH_POINTER_ACCESSIBILITY_BEST_PRACTICES.md此技能源自该文件。如有疑问,以示例为准。
实现任何供用户触摸、单击、轻触、拖动或通过手势操作的交互式 UI 时,请遵循这些规则。
请与 keyboard/SKILL.md 一同加载——指针和键盘要求相辅相成,不能相互替代。
核心要求
构建适用于触摸屏、鼠标、触控板、触控笔、头控指针、开关控制指针及其他指针设备的界面。不要根据用户使用的设备推断其能力——触摸屏用户也可能使用键盘、语音输入、屏幕阅读器或鼠标。
仅提供键盘替代方案本身并不能满足指针手势(2.5.1)或拖动(2.5.7)要求——替代方案必须支持使用单个指针操作,且无需基于路径的手势或拖动;尽管 2.1.1 另有键盘可操作性要求。原生按钮可以轻松同时满足这两项要求。
严重程度级别(本技能)
| 级别 | 含义 |
|---|---|
| 严重 | 功能只能通过多点手势使用,且没有单指针替代方案;user-scalable=no 会阻止缩放 |
| 较高 | 拖动排序没有单指针替代方案;主要操作的触摸目标小于 24×24px |
| 中等 | 按下鼠标或触摸时即触发操作,且没有松开事件取消机制;动作手势没有 UI 替代方案 |
| 较低 | 非主要操作的目标小于 44×44px;目标之间的间距过小 |
请根据对用户的实际影响、任务关键性、可达性和使用频率确定优先级——不要仅根据成功标准编号或工具输出分配严重程度。
从不依赖输入方式的基础实现开始
使用原生 HTML 控件和链接——它们内置的 click 激活机制适用于鼠标、触摸、触控笔、键盘和许多辅助技术(AT)。
<div class="button" ontouchend="saveChanges()">Save changes</div>
<button type="button" id="save-button">Save changes</button>
<script>
document.querySelector("#save-button").addEventListener("click", saveChanges);
</script>
对于自定义直接操作组件,请使用 Pointer Events,而不是分别维护鼠标和触摸实现。不要根据 pointerType 限制核心功能——使用触控笔的用户不应失去鼠标用户可以使用的功能。
严重:切勿阻止缩放(user-scalable=no)
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="viewport" content="width=device-width, initial-scale=1">
某些库或框架会自动添加 user-scalable=no——请检查每个项目的 viewport meta 标签。不要依赖浏览器忽略限制性 viewport 设置;应从源代码中移除限制。在 200% 缩放下测试文本,在 400% 缩放下测试布局。
严重:为多点/路径手势提供单指针替代方案(WCAG 2.5.1)
多点手势需要使用两个或更多接触点(捏合缩放)。基于路径的手势取决于描绘的路线、方向或形状,而不仅仅是起点和终点。当内容定义了这类手势时,必须提供一种使用单个指针、无需基于路径的手势即可执行相同功能的方式——替代方案必须存在于内容中,而不能只是键盘命令。
<div class="map-controls" aria-label="Map controls">
<button type="button" data-map-action="zoom-in">Zoom in</button>
<button type="button" data-map-action="zoom-out">Zoom out</button>
<button type="button" data-map-action="north">Pan north</button>
</div>
示例:用于缩放或平移地图的按钮;轮播图的上一张/下一张按钮;绘制形状之外的菜单命令;可见的旋转/调整大小控件。浏览器或辅助技术本身要求的手势(例如移动设备屏幕阅读器的导航手势)不受作者控制——不要复现或干扰这些手势。
严重:不使用拖动的替代方式(WCAG 2.5.7 — WCAG 2.2)
如果某项功能使用拖动,必须提供一种仅用单指针、无需拖动即可完成操作的方式——仅提供键盘替代方式本身并不满足 2.5.7。原生按钮可同时满足指针操作要求和键盘访问需求。
<ul id="task-list">
<li>
<span>Review content</span>
<button type="button" data-move="up" aria-label="Move up: Review content">Move up</button>
<button type="button" data-move="down" aria-label="Move down: Review content">Move down</button>
</li>
</ul>
<p id="reorder-status" role="status" aria-atomic="true"></p>
taskList.addEventListener("click", (event) => {
const button = event.target.closest("button[data-move]");
if (!button) return;
const item = button.closest("li");
const label = item.querySelector("span").textContent;
const direction = button.dataset.move;
if (direction === "up" && item.previousElementSibling) item.previousElementSibling.before(item);
else if (direction === "down" && item.nextElementSibling) item.nextElementSibling.after(item);
else return;
updateMoveButtons();
reorderStatus.textContent = `${label} moved ${direction}.`;
});
可以将拖放作为增强功能保留。不使用拖动的控件必须提供相同的结果、始终可见或易于发现,并在移动后保留焦点和状态。其他模式:选择项目 + 选择目的地 + 激活“移动”;点击地图控件进行平移;拖动滑块滑块的同时,也允许输入数值或点击轨道。
严重:指针取消(WCAG 2.5.2)
普通控件应在 click 时激活,而非在 pointerdown/mousedown/touchstart 时激活。
// Right — fires on click; user can drag away to cancel
deleteButton.addEventListener("click", deleteItem);
WCAG 允许采用多种模式:(1)按下事件不执行功能;(2)功能在抬起事件时完成,且用户可在释放前取消,或在完成后撤销;(3)释放时撤销按下事件的结果;(4)必须在按下时完成。对于具有破坏性或难以撤销的操作,还应要求确认或提供“撤销”功能:
<button type="button" id="archive-button">Archive message</button>
<p id="archive-status" role="status"></p>
<button type="button" id="undo-archive" hidden>Undo archive</button>
严重:目标尺寸下限(WCAG 2.5.8 — WCAG 2.2)
指针目标至少须为 24×24 CSS 像素,以下情况除外:
- 间距 — 以目标为圆心、直径为 24px 的圆不会与其他目标对应的圆相交(这是特定的几何测试,而不只是“添加一些外边距”)
- 等效 — 同一页面上另有一个执行相同功能且符合尺寸要求的控件
- 行内 — 目标位于句子中,尺寸受行高限制
- 用户代理控件 — 尺寸由浏览器决定,作者无法修改
- 必要 — 特定呈现方式不可或缺或为法律要求
对于重要或常用的控件,建议默认使用 44×44 CSS 像素——与依赖间距例外相比,这种做法更容易实现和测试,而且也达到了 WCAG 2.5.5(AAA)增强型目标尺寸的阈值。
.icon-button {
display: inline-grid;
min-inline-size: 2.75rem; /* 44px at default 16px root */
min-block-size: 2.75rem;
padding: 0.625rem;
place-items: center;
}
.icon-button svg { inline-size: 1.5rem; block-size: 1.5rem; }
使用 rem,让目标随用户默认文字大小一起增大。将内边距设在交互元素本身,而不是非交互式容器上——整个内边距区域都必须可点击:
<a class="nav-link" href="/account/">Account</a>
.nav-link { display: inline-flex; min-block-size: 2.75rem; padding: 0.625rem 0.875rem; align-items: center; }
不要对所有 <a> 一律设置最小宽度或高度——行内正文链接有明确的例外,强行将其设为方框会影响阅读和换行。将复选框或单选框与可见的 <label> 元素关联,让标签扩大可操作区域。
严重:动作触发的替代方式(WCAG 2.5.4)
如果摇晃、倾斜或对着摄像头做手势会执行某项功能,则必须为同一功能提供常规 UI 控件;允许用户关闭动作触发;且仅在用户明确执行某项操作后请求传感器权限。
<button type="button" id="undo-button">Undo last change</button>
<label><input type="checkbox" id="shake-toggle"> Enable shake to undo</label>
无论动作输入是否启用,都应保留常规按钮——传感器功能默认关闭。
严重:不要依赖悬停
悬停时显示的每项功能,都必须通过常驻控件或键盘焦点访问——触摸设备可能无法可靠地支持悬停。
<button type="button" aria-expanded="false" aria-controls="account-menu">Account menu</button>
<ul id="account-menu" hidden>…</ul>
除非符合例外情况,否则悬停或聚焦时显示的内容必须可关闭、可悬停且保持显示(WCAG 1.4.13)。对于重要说明,可见按钮或展开控件通常比工具提示更可靠。
中等:谨慎使用 pointer/hover 媒体特性
pointer/hover 描述的是主要设备;any-pointer/any-hover 则报告所有可用设备的能力。这些特性无法检测键盘操作、识别残障,也无法可靠地将每支触控笔都归类为粗略指针,或将每块触摸屏都识别为主设备——混合型设备可能报告多种能力,而且这些能力可能在页面打开期间发生变化。
.toolbar button { min-inline-size: 2.75rem; min-block-size: 2.75rem; }
@media (any-pointer: coarse) { .toolbar { gap: 0.75rem; } }
绝不要通过精确指针查询隐藏重要控件:
/* Incorrect: touch and keyboard users may lose the controls */
@media (pointer: fine) { .editing-controls { display: flex; } }
先采用无障碍的基础布局,再仅通过媒体查询添加增强功能。
中等:自定义交互优先使用指针事件
surface.addEventListener("pointerdown", (event) => {
if (activePointerId !== null) return;
activePointerId = event.pointerId;
surface.setPointerCapture(event.pointerId);
beginPreview(event.clientX, event.clientY);
});
surface.addEventListener("pointerup", (event) => {
if (event.pointerId !== activePointerId) return;
activePointerId = null;
commitInteraction(event.clientX, event.clientY);
});
surface.addEventListener("pointercancel", (event) => {
if (event.pointerId === activePointerId) cancelInteraction();
});
surface.addEventListener("lostpointercapture", () => {
if (activePointerId !== null) cancelInteraction();
});
跟踪活动的 pointerId;在 pointerup 时结束;在 pointercancel 时妥善取消;处理 lostpointercapture;不要假设每个指针都会报告压力、倾斜度或尺寸;仍需为相同功能提供非手势、非拖动的控件。对于同一操作,不要同时注册 touchend 和 click,除非能防止重复触发。
中等:使用 touch-action 保留浏览器平移和缩放
/* A horizontal carousel handles horizontal movement;
preserve vertical page scroll and pinch zoom */
.carousel-viewport { touch-action: pan-y pinch-zoom; }
仅在需要自定义交互的最小表面区域使用 touch-action。切勿在 html、body、页面容器、地图、画布或轮播图上宽泛应用 touch-action: none——这会禁止从该元素开始的浏览器平移和缩放。在限制手势之前,请确认:组件确实需要处理该手势;用户仍可从组件区域滚动页面;仍可使用双指缩放;简单控件能提供相同功能。相比在每次触摸移动时调用非被动的 preventDefault(),touch-action 更可取。
组件模式
| 组件 | 必需做法 |
|---|---|
| 轮播图 | 提供上一个/下一个/暂停控件;不强制用户滑动;保留滚动功能并支持减少动态效果 |
| 地图 | 提供有明确名称的缩放/平移控件;如果仅靠可视地图不足以完成操作,则提供地址/坐标/列表等替代方式 |
| 滑块 | 优先使用 <input type="range">;除拖动滑块外,还要提供数字输入框或点击轨道的方式 |
| 可排序列表 | 除拖放外,还要提供上移/下移/移至控件;播报结果并保留焦点 |
| 滑动操作 | 通过可见按钮/菜单提供该操作;不能只靠滑动来删除/归档/显示内容 |
| 长按 | 提供常规按钮/菜单;不能作为使用某项功能的唯一途径 |
| 绘图/签名 | 非必要时避免要求精细的路径精度;在任务允许时提供输入文字/上传文件等替代方式 |
| 画布控件 | 为所有操作、名称、值和结果提供无障碍 DOM 界面——仅有画布像素不具备任何语义 |
不要依赖双击、压力、倾斜、边缘滑动或特定设备的手势来实现基本功能。
严重:在无障碍名称中保留可见标签(WCAG 2.5.3)
语音输入用户(Dragon、iOS Voice Control)通常会通过说出控件的可见标签来激活控件——无障碍名称必须包含该文本。
<button type="button">Send</button>
<button type="button" aria-label="Send application">Send</button>
<button type="button" aria-label="Submit application">Send</button>
将可见标签放在较长无障碍名称的开头,是便于语音输入的一种实用惯例。仅图标按钮没有可见标签可供 2.5.3 标准核对,但仍需符合 4.1.2 标准,提供清晰的无障碍名称——使用常见图标,并在空间允许时提供可见标签;工具提示不能代替始终可见的标签。
保持方向、重排与可操作性
除非某个方向不可或缺,否则应支持纵向和横向;不要用方向锁定来弥补布局缺乏弹性的问题。在窄屏和高缩放级别下:保持控件顺序合理;换行显示工具栏,而不是将目标缩小到最小尺寸以下;避免粘性页眉/横幅/聊天组件遮挡获得焦点的控件;确保对话框/菜单位于视口内;除非真正的二维内容确有需要,否则避免页面横向滚动。打开屏幕键盘时,必须确保获得焦点的输入框/错误信息/提交控件仍然可见,或可滚动至视野内。
兼顾触摸屏阅读器
移动设备上的屏幕阅读器会改变触摸输入传递到页面的方式——用户可能通过触摸探索、滑动浏览无障碍树,并使用屏幕阅读器的激活手势,而不是直接轻触。使用原生元素和准确的无障碍名称;确保无障碍目标与可见控件对齐;公开当前状态(展开/选中/已勾选/值);避免自定义手势与辅助技术手势冲突;不要要求用户具备空间方位认知(“轻触右上角的形状”)。分别测试关闭屏幕阅读器时的直接触摸,以及开启屏幕阅读器时的触摸探索——其中一种方式通过测试,并不代表另一种也能正常工作。
防止意外激活和数据丢失
触摸输入可能不精确或被中断。对于影响重大的操作: 将相邻的破坏性操作和建设性操作分开;使用清晰的标签,而不是含义模糊的图标;不要在指针按下时执行操作;对难以撤销的操作进行确认;在可行时提供撤销功能;在验证错误、屏幕方向变化或暂时断开连接后保留已输入的数据。目标尺寸并非唯一的防护措施——如果“删除”按钮旁边就是“保存”按钮,且没有恢复途径,即使按钮很大,也仍可能造成损害。
测试
- 审查: 列出所有控件和手势;识别多点触控、基于路径的交互、拖动、滑动、长按、压力、倾斜和动作交互;确认每种交互都有所需的单指替代方式(不仅限于键盘);以 CSS 像素测量实际目标框;查找受限的视口设置和宽泛的
touch-action规则;确认可见标签包含在无障碍名称中 - 输入: 仅使用触摸、仅使用鼠标/触控板、使用触控笔(如支持)以及仅使用键盘完成每项任务;测试语音输入对可见标签的识别;使用移动设备屏幕阅读器测试触摸探索和激活;测试可在输入模式间切换的混合设备。最终测试请使用实体设备——仿真无法可靠复现手部遮挡、触及范围、意外触碰或传感器权限
- 视觉/布局: 竖屏/横屏;200%/400% 缩放;支持的最小视口搭配屏幕键盘;粘性/固定内容不遮挡控件;从每个自定义手势区域进行捏合缩放和页面滚动
- 交互状态: 按下、移开、松开——确认需要时可以取消操作;通过滚动/屏幕方向变化/指针捕获丢失来中断直接操控交互;确认拖动替代方式能产生相同结果并保留焦点;确认可以禁用动作输入,同时仍可使用其替代方式
自动化检查可以识别一些尺寸过小的目标、无效名称、受限的视口设置和重复的事件模式——但无法判断某种手势是否必不可少、替代方式是否等效且容易发现,也无法判断触摸屏阅读器交互是否易于理解。仍需进行人工测试。
常见问题
| 问题 | 修正方法 |
|---|---|
| 布局会错乱,因此禁用了浏览器缩放 | 修复响应式布局;移除视口缩放限制 |
| 键盘命令是捏合/路径手势的唯一替代方式 | 添加无需路径手势、可用单指操作的控件 |
| 只提供键盘重新排序作为拖动的替代方式 | 添加无需拖动的单指“移动”控件 |
为了让操作更灵敏,在 pointerdown 时触发 | 在 click/pointerup 时完成操作,并支持取消或撤销 |
| “每个目标都必须为 24×24px,没有例外” | 按照 2.5.8 的确切规则及其规定的例外处理 |
| 声称模糊的间距可让尺寸不足的目标符合要求 | 检查是否通过实际的 24px 圆形测试 |
| 强制要求每个链接都使用方形目标 | 保持行内正文链接处于正常排版流中 |
| 在小图标按钮外的包装元素上添加内边距 | 将内边距加在交互元素本身上 |
| 控件仅在悬停时显示 | 提供常驻控件或由焦点触发的控件,以及可通过触摸操作的展开选项 |
将 pointer: coarse 视为可靠的触摸屏/触控笔检测方式 | 将媒体特性视为能力指标;基础体验应兼容各种输入方式 |
仅在匹配 pointer: fine 时显示必要控件 | 在基础布局中保留核心控件 |
| 分别处理触摸和鼠标事件导致操作触发两次 | 使用原生 click 或统一的 Pointer Events 实现 |
对整个页面/组件应用 touch-action: none | 保留平移/缩放;仅限制必要的手势轴 |
| 动作输入始终处于启用状态且无法关闭 | 提供常规替代方式,并让用户能够关闭该功能 |
| 只使用移动模拟器测试触摸 | 测试实体设备、混合输入方式和移动屏幕阅读器 |
| 将键盘测试通过视为指针无障碍的证明 | 分别测试手势、拖动、取消和目标尺寸 |
完成标准清单
- 视口 meta 标签中没有
user-scalable=no和maximum-scale - 所有多点/基于路径的手势功能都在内容中提供单指替代方式(不能只有键盘命令)
- 所有拖动功能都提供单指、无需拖动的替代方式
- 交互目标:最小尺寸为 24×24px(较小时需符合有效例外); 主要控件推荐为 44×44px
- 小目标的内边距位于交互元素本身,而非包装元素上
- 操作在释放事件时触发(
click、pointerup);破坏性操作可确认/撤销 - 设备动作功能有 UI 替代方式,并且可以关闭
-
pointer/hover媒体查询只用于增强本已无障碍的基础体验 ——绝不隐藏必要控件 - 自定义交互使用 Pointer Events,并处理
pointercancel/lostpointercapture -
touch-action的作用范围足够小;页面滚动和捏合缩放在其他区域保留 -
aria-label值以可见标签文字开头或包含该文字(WCAG 2.5.3) - 粘性/固定/覆盖内容不会遮挡获得焦点或可操作的控件
- 在实体设备上测试:iOS VoiceOver(触摸)、TalkBack(Android)、 鼠标/触控板、仅使用键盘
WCAG 关键标准
- 1.3.4 方向(AA)
- 1.4.4 调整文本大小(AA)——缩放受阻时风险极高
- 1.4.10 重排(AA)
- 1.4.13 悬停或焦点上的内容(AA)
- 2.1.1 键盘(A)——除指针替代方式外,还必须单独满足
- 2.5.1 指针手势(A)——多点手势没有单指替代方式时,问题严重
- 2.5.2 指针取消(A)——在按下事件时触发操作,问题严重
- 2.5.3 名称中的标签(A)——对语音控制用户影响严重
- 2.5.4 动作触发(A)
- 2.5.5 目标尺寸增强(AAA)——44×44px
- 2.5.7 拖动操作(AA,WCAG 2.2)——拖动没有单指替代方式时,问题严重
- 2.5.8 最小目标尺寸(AA,WCAG 2.2)——小于 24×24px 时,问题严重
参考资料
- 完整的最佳实践指南
- WCAG 2.2 理解 2.5 输入方式
- WCAG 2.2 理解 2.5.7 拖动操作
- WCAG 2.2 理解 2.5.8 目标尺寸下限
- Pointer Events Level 3
- MDN — 指针事件
- MDN — touch-action
标准展望: WCAG 2.5.x 标准是近期新增的内容(2.1 和 2.2)。 预计 WCAG 3.0 将进一步扩展指针和触摸相关要求。 请关注:https://www.w3.org/TR/wcag-3.0/