移动端无:hover状态,因触控设备无悬停动作;iOS/Android仅模拟一次且难清除;需用@media (hover: hover) and (pointer: fine)检测可靠悬停,或用:active+JS模拟。

移动端压根没有:hover这个状态——不是你写错了,也不是浏览器bug,而是触控设备物理上不存在“悬停”动作,浏览器因此不触发或只模拟一次,之后就卡住。
为什么真机上:hover样式被划掉或点击后不消失
iOS Safari 和 Android Chrome 会把第一次 touchstart 当作:hover的起点,但没有对应的“手指移开”事件来结束它。滚动、切页、点空白处才可能清除,不保证及时。你在 DevTools 里看到 .btn:hover 规则被划掉,大概率是伪类根本没激活,而不是 CSS 编译或语法问题。
- 元素没被识别为可交互目标(缺少
cursor: pointer或tabindex="0") - 父容器设了
overflow: hidden,hover 后的transform或box-shadow被裁剪,导致 hit-test 失败 - 用了
pointer-events: none或被遮挡层挡住 - 在非可点击语义元素(如
<div>)上直接写:hover,iOS Safari 默认禁用
@media (hover: hover) and (pointer: fine) 怎么写才起作用
这是唯一符合规范的检测方式,表示“当前输入设备支持可靠悬停 + 指针精度高”,即鼠标或触控板场景。它不是判断“是不是手机”,而是判断“能不能悬停”。
- 必须带值:
@media (hover: hover)才有效;@media (hover)整条规则会被忽略 - 必须搭配
(pointer: fine),排除触控笔等粗粒度输入误判 - 必须放在常规样式后面,否则会被
.btn:hover覆盖 - 不要用
@media (hover: none)——多数现代浏览器根本不报告这个值 - Tailwind 不内置该查询,得手写 CSS 块,例如:
@media (hover: hover) and (pointer: fine) { .card:hover { @apply -translate-y-1 transition-transform duration-300; } }
:active 是移动端最稳的瞬时反馈替代方案
:active 在手指按下瞬间触发、松开即退,行为接近 PC 端的 :hover + :active 组合,但它在 iOS Safari 默认不生效,除非满足以下任一条件:
立即学习“前端免费学习笔记(深入)”;
- 元素或其祖先有
cursor: pointer - 元素或其祖先绑定了
touchstart事件(最简做法:<body ontouchstart="">)
注意::active 动画持续时间建议 ≤ 0.15s;别用 !important 强行覆盖 :hover,而是让 :active 自身定义完整状态(背景、缩放、边框等)。
需要维持悬停态?JS 模拟 is-hovered 类要避开三类坑
如果交互依赖“悬停态维持”(比如下拉菜单、图片放大),不能只靠伪类。JS 模拟是最可控路径,但容易踩坑:
- 触摸穿透:快速连点多个元素,前一个
is-hovered可能残留——建议统一清理或用setTimeout(() => el.classList.remove('is-hovered'), 0) - 滚动干扰:页面滚动时触发
touchstart,误判为点击——需监听touchcancel并清理 - 内存泄漏:单页应用中组件反复挂载/卸载,没解绑事件就会累积监听器——优先用事件委托,在父容器监听
touchstart,再用event.target.matches('[data-hoverable]')判断目标
真正卡住人的,往往不是 hover 本身,而是父容器的 overflow: hidden、未设 position: relative 导致 z-index 失效,或者 JIT 模式没开导致新写的 hover: 类根本没进编译结果。


















