:hover 在移动端“粘住不退”是浏览器延迟清理悬停状态所致;@media (hover: hover) 单独使用失效因它仅表示设备支持悬停而非当前输入方式,须叠加 (pointer: fine) 才精准匹配鼠标/触控笔;:active 伪类需满足 button 元素或绑定 touchstart 才生效,且动画建议 ≤0.15s 并组合 scale 与 opacity;js 模拟 is-hovered 类时须 preventdefault、延迟清除、跳转前手动清除。

:hover 在移动端“粘住不退”不是 bug,是浏览器对悬停状态的延迟清理机制导致的。 iOS Safari 和部分 Android Chrome 会把首次触摸当作 hover 开始,但没有对应的结束事件,直到滚动、焦点切换或页面重绘才会清除状态——所以你点完按钮,样式卡在高亮态,再点别处也没用。
为什么 @media (hover: hover) 单独用会失效
这个媒体查询在 iPad、Surface 等混合设备上恒为 true,因为它只表示“设备声明支持 hover”,不反映当前实际输入方式。手指触控时 hover: hover 仍为 true,但 pointer: coarse 才是真实指针类型。单独依赖它,等于没判。
- 必须叠加
@media (hover: hover) and (pointer: fine)才能精准锁定鼠标/触控笔场景 -
@media (any-hover: hover)更不可靠,连蓝牙键盘附带的触控板都会被算进来 - W3C 明确规定
(hover: hover) and (pointer: coarse)永远不匹配,别写
如何用 :active + JS 防止触摸残留
:active 是移动端唯一稳定可依赖的“按下态”伪类,但它默认在 iOS Safari 不生效,除非满足以下任一条件:
- 元素是
<button></button>或带role="button"且有tabindex - 绑定了
touchstart事件(最简做法:) - 注意:仅声明
cursor: pointer在移动端无效,CSS 层面被忽略
另外,:active 动画建议 ≤ 0.15s;纯改背景色反馈弱,推荐组合 transform: scale(0.98) + opacity: 0.9;若父容器设了 overflow: hidden,缩放可能被裁剪。
JS 模拟 is-hovered 类时最容易漏掉的三件事
用 touchstart 添加类看似简单,但真机上常因细节缺失导致状态错乱:
- 必须调用
e.preventDefault(),否则安卓部分机型会触发双击缩放或中断滚动 - 别在
touchend立即移除类——用户滑动时touchend可能不触发,推荐用setTimeout延迟 300ms 清除 - 如果元素有跳转行为(如
<a href="/page"></a>),要在click回调里立即清除is-hovered,否则新页面加载后样式残留
真正难的从来不是怎么加 hover,而是怎么让它的边界在不同输入方式之间自然消失——它藏在 pointer: fine 的判定里,藏在 touchcancel 的监听里,也藏在你忘了给父容器加 will-change: transform 的那一行注释里。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











