触摸屏上:hover和:focus“失效”实为行为逻辑变化:iOS Safari首次触摸短暂触发:hover后立即进入:focus(易因软键盘丢失),Android Chrome则常跳过:focus直接点击;:focus-visible可精准区分键盘导航与触控操作,避免视觉闪烁,是现代可访问性推荐方案。

触摸屏上:hover和:focus为什么看起来“失效”
不是伪类失效,而是行为逻辑变了:iOS Safari 会在首次触摸后短暂触发 :hover,随后立即进入 :focus(如果元素可聚焦),但焦点又常因软键盘弹出或页面重排快速丢失;Android Chrome 则可能跳过 :focus 直接执行点击。结果就是你写了 button:hover 和 button:focus,却在真机上只看到一闪而过的悬停色,或干脆没任何反馈。
用:focus-visible替代:focus做键盘专属反馈
:focus-visible 是现代浏览器(Chrome 86+、Firefox 84+、Safari 15.4+)原生支持的解决方案,它只在用户通过键盘 Tab 导航触发焦点时生效,鼠标点击后不会激活——这正好避开触摸屏上“点一下就闪 outline”的问题。
- 别再全局写
*:focus { outline: none; },这是可访问性地雷 - 正确做法是先保留默认
outline,再用:focus-visible做增强:button:focus { outline: none; }<br>button:focus-visible { outline: 2px solid #007bff; outline-offset: 2px; } - 对不支持
:focus-visible的旧 Safari(@supports 回退:@supports not (focus-visible: auto) {<br> button:focus { outline: 2px solid #007bff; }<br>}
给非表单元素加 tabindex="0" 才能真正响应:focus
像 <div class="card"> 这类自定义控件,默认无法获得焦点,:focus 样式自然不生效。必须显式声明 tabindex="0",且建议同步加 role="button" 和 aria-label,否则屏幕阅读器也读不出来。
-
tabindex="0"表示该元素可被 Tab 键顺序聚焦,且保留在默认焦点流中 - 别用
tabindex="-1"来“临时聚焦”——它无法通过键盘到达,只适合 JS 主动调用.focus() - 若元素只是视觉容器(比如纯展示卡片),不要硬加
tabindex,改用:active或 JStouchstart类控制反馈
移动端必须搭配:active + JS touch 事件兜底
:hover 在绝大多数触摸设备上不可靠,:focus 又受制于软键盘行为,唯一稳定的是 :active——所有现代浏览器都支持,点击瞬间即生效,无需任何额外属性。
立即学习“前端免费学习笔记(深入)”;
- 基础保障:
button:active, .card:active { opacity: 0.8; transform: scale(0.98); } - 更稳妥的做法是监听
touchstart并添加临时 class,避免 300ms 延迟和重复触发:element.addEventListener('touchstart', e => {<br> e.target.classList.add('is-pressed');<br> setTimeout(() => e.target.classList.remove('is-pressed'), 150);<br>}); - 注意:iOS Safari 对
:active有特殊限制——必须给元素设cursor: pointer或绑定 click 事件,否则不触发
:focus 当作跨设备通用状态来用,却没意识到触摸屏上它既不稳定、也不可视;真正可靠的交互反馈,得靠 :active 打底 + :focus-visible 补键盘 + touchstart 补 iOS 短暂悬停。


















