触屏浏览器不实现真正的:hover,因其无悬停物理动作;ios/android仅在touchstart时模拟一次:hover且不维持;应使用@media (hover: hover) and (pointer: fine)精准启用,并配合:active或js模拟状态。

直接结论:不是你写错了,是触屏浏览器压根不实现真正的 :hover —— 它没有“悬停”这个物理动作,所以不能靠它做交互反馈。
为什么 :hover 在 iOS Safari 和 Android Chrome 里“卡住”或“不触发”
触屏设备上没有鼠标指针,浏览器无法判断手指是准备点击、划过,还是悬停。于是主流移动浏览器做了妥协:touchstart 时模拟一次 :hover,但之后既不维持,也不自动退出。结果就是点完按钮背景色挂着不动,滚动或切 Tab 才可能清掉——这不是 bug,是规范行为。
- iOS Safari 会把元素标记为“已 hover”,直到焦点切换才释放
- 原生
<button></button>自带高亮逻辑,和 CSS:hover并行运行,容易打架 - 微信/QQ 内置浏览器(X5 内核)甚至忽略
:hover状态更新,残留更顽固
用 @media (hover: hover) and (pointer: fine) 精准启用 hover 动画
这条媒体查询才是唯一靠谱的“守门员”,它要求设备既支持悬停,又使用精细指针(鼠标/触控笔),自动排除所有手指触控场景。
- 别单独用
@media (hover: hover):iPad 接妙控板时也返回 true,但用户正用手指点,结果 hover 效果“卡开” - 别写
@media (hover: hover) and (pointer: coarse):W3C 规范里二者互斥,这条永远不匹配 - 必须把整段
:hover规则(包括transition)都包进去,否则移动 Safari 可能解析到动画却不用 - 规则必须放在常规样式之后,否则会被
.btn:hover直接覆盖
@media (hover: hover) and (pointer: fine) {
.card:hover {
@apply -translate-y-1 transition-transform duration-300;
}
}
移动端必须有 :active 替代反馈,且 iOS Safari 要手动激活
:active 是触屏下最稳的瞬时反馈方式,但它在 iOS Safari 默认不生效,除非满足以下任一条件:
远程控制 Android 设备,支持截图、自动化操作手机 App 等功能。本应用使用 uiautomator2 作为底层框架,通过 ADB 连接设备,用户无需 Root(1)查看手机屏幕;(2)自动化操作手机 App;(3)远程安装/卸载应用……
- 元素或其任意祖先绑定了
touchstart事件(最简做法:) - 元素本身是
<button></button>或带role="button"且有tabindex="0"
:active 动画建议 ≤ 0.15s;别只改背景色,组合 transform: scale(0.98) + opacity: 0.9 更可靠;还要检查父容器是否设了 overflow: hidden,否则缩放会被裁剪。
需要“持续悬停态”?用 JS 模拟 is-hovered 类
如果交互依赖状态维持(比如菜单展开、图片放大),伪类不够用,JS 模拟是最可控路径,但有三个关键细节常被忽略:
- 监听
touchstart添加is-hovered类时,必须调用e.preventDefault(),否则安卓部分机型会触发双击缩放 - 别在
touchend立即移除类——用户滑动时touchend不一定触发,推荐用setTimeout延迟 300ms 清除 - 若元素有跳转行为(如
<a href></a>),要在click回调里立即清除is-hovered,否则新页面样式被污染
真正难的从来不是怎么写 :hover,而是怎么让它的边界在不同输入方式之间自然消失——它藏在 pointer: fine 的判定里,也藏在你忘了给父容器加 will-change: transform 的那一行注释里。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










