语义化标签不替换会导致触控和可访问性直接失效:被跳过无焦点,自带键盘支持与触控反馈;所有点击容器必须用或显式绑定;dom深度≥6层会使touchstart延迟翻倍;viewport缺user-scalable=no则300ms延迟仍存在。

语义化标签不替换,触控和可访问性直接失效
移动端用户依赖 VoiceOver 或 TalkBack 导航时,<div class="btn"> 和 <code><button></button> 的行为天差地别:前者被跳过、无焦点、无法用 Space/Enter 触发;后者自带键盘支持、:active 状态、触控反馈通道。这不是“样式问题”,而是 DOM 层级缺失语义导致的底层中断。
- 所有带点击行为的容器必须用
<button></button>,哪怕只执行 onclick="toggleMenu()"
-
<label></label> 必须显式包裹 <input type="checkbox">,或用 for 属性绑定 ID,否则点击文字区域无效
- 禁用
role="button" 伪装——它不自动获得 tab 键焦点,:active 不触发,部分 WebView(如 Android 低版本系统 WebView)完全忽略
- 真机测试时双指滑动切换焦点,若某个“按钮”被跳过,基本就是语义缺失
DOM 深度超过 6 层,touchstart 延迟翻倍
浏览器事件冒泡路径随嵌套层数线性增长。实测在低端安卓设备上,body > div > div > div > div > div > button 这种 6 层结构,touchstart 到 JS handler 执行平均延迟 42ms;而 <main><nav><button></button></nav></main> 三层结构稳定在 8–12ms。
- Chrome DevTools → Elements 面板右键任意元素 →
Show DOM properties 查看 depth 值,≥ 6 就该重构
- 避免为加 class 或 JS 选择器硬套
<div>,改用 <code>display: contents(Safari 15.4+ 支持)抹除无意义包裹层
- 动态插入菜单项时,用
DocumentFragment 批量 append,别循环调用 appendChild——每次都会触发 layout,拖慢触控响应
viewport 配置缺 user-scalable=no,300ms 延迟照常发生
移动端浏览器默认保留双击缩放能力,为此会等待 300ms 判断是否为双击操作。即使写了 width=device-width,若没配 user-scalable=no 或 initial-scale=1.0,这个延迟就无法绕过。
- 完整 viewport meta 应为:
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
- 不要只写
width=device-width,漏掉 user-scalable=no 是常见疏忽
- 部分 CMS 或框架自动生成的 meta 会覆盖手动配置,需检查最终渲染出的 HTML 源码
触摸目标尺寸 ≠ CSS 视觉尺寸,clientWidth
iOS 和 Android 辅助技术判断可触区域,依据的是元素的 clientWidth × clientHeight(单位:CSS 像素),不是 padding + 字体撑开的“看起来像按钮”的区域。很多小图标按钮实际渲染尺寸只有 24×24px,VoiceOver 焦点环直接跳过。
- 用 Chrome DevTools 切换设备模拟 → Inspect 元素 → 查看右侧
Computed 中的 width 和 height 值
- 图标按钮如
<button><svg></svg></button> 必须显式设 min-width: 48px; min-height: 48px;
- 相邻可触元素间距至少
8px,否则 iOS VoiceOver 滑动切换焦点时容易连跳两个
- 禁用
font-size: 12px + padding: 2px 6px 拼小按钮——计算后 clientWidth 很可能不到 48px
真实项目里最容易被忽略的,是 DOM depth 和 clientWidth 这两个数值型指标:它们不报错、不警告,但一上线就在低端机和辅助技术场景下暴露问题。测的时候别只看“点得动”,要打开 VoiceOver、用真机滑动焦点、查 DevTools 的 depth 和 clientWidth。
移动端用户依赖 VoiceOver 或 TalkBack 导航时,<div class="btn"> 和 <code><button></button> 的行为天差地别:前者被跳过、无焦点、无法用 Space/Enter 触发;后者自带键盘支持、:active 状态、触控反馈通道。这不是“样式问题”,而是 DOM 层级缺失语义导致的底层中断。
- 所有带点击行为的容器必须用
<button></button>,哪怕只执行onclick="toggleMenu()" -
<label></label>必须显式包裹<input type="checkbox">,或用for属性绑定 ID,否则点击文字区域无效 - 禁用
role="button"伪装——它不自动获得 tab 键焦点,:active不触发,部分 WebView(如 Android 低版本系统 WebView)完全忽略 - 真机测试时双指滑动切换焦点,若某个“按钮”被跳过,基本就是语义缺失
DOM 深度超过 6 层,touchstart 延迟翻倍
浏览器事件冒泡路径随嵌套层数线性增长。实测在低端安卓设备上,body > div > div > div > div > div > button 这种 6 层结构,touchstart 到 JS handler 执行平均延迟 42ms;而 <main><nav><button></button></nav></main> 三层结构稳定在 8–12ms。
- Chrome DevTools → Elements 面板右键任意元素 →
Show DOM properties查看depth值,≥ 6 就该重构 - 避免为加 class 或 JS 选择器硬套
<div>,改用 <code>display: contents(Safari 15.4+ 支持)抹除无意义包裹层 - 动态插入菜单项时,用
DocumentFragment批量append,别循环调用appendChild——每次都会触发 layout,拖慢触控响应 - 完整 viewport meta 应为:
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> - 不要只写
width=device-width,漏掉user-scalable=no是常见疏忽 - 部分 CMS 或框架自动生成的 meta 会覆盖手动配置,需检查最终渲染出的 HTML 源码
- 用 Chrome DevTools 切换设备模拟 → Inspect 元素 → 查看右侧
Computed中的width和height值 - 图标按钮如
<button><svg></svg></button>必须显式设min-width: 48px; min-height: 48px; - 相邻可触元素间距至少
8px,否则 iOS VoiceOver 滑动切换焦点时容易连跳两个 - 禁用
font-size: 12px + padding: 2px 6px拼小按钮——计算后 clientWidth 很可能不到 48px
viewport 配置缺 user-scalable=no,300ms 延迟照常发生
移动端浏览器默认保留双击缩放能力,为此会等待 300ms 判断是否为双击操作。即使写了 width=device-width,若没配 user-scalable=no 或 initial-scale=1.0,这个延迟就无法绕过。
触摸目标尺寸 ≠ CSS 视觉尺寸,clientWidth
iOS 和 Android 辅助技术判断可触区域,依据的是元素的 clientWidth × clientHeight(单位:CSS 像素),不是 padding + 字体撑开的“看起来像按钮”的区域。很多小图标按钮实际渲染尺寸只有 24×24px,VoiceOver 焦点环直接跳过。











