移动端无:hover状态,因触控设备物理上不存在悬停动作;浏览器仅在首次touchstart时模拟一次且无法清除,需用@media (hover: hover) and (pointer: fine)精准检测、:active替代或js模拟is-hovered类。

移动端压根没有 :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>)上直接写 <code>: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(注意:纯 CSS 声明在移动端被忽略,无效) - 元素或其祖先绑定了
touchstart事件(最简做法:) - 元素本身是
<button></button>或带role="button"且有tabindex
另外:
:active动画持续时间建议 ≤ 0.15s;别用!important强行覆盖:hover,而是让:active自身定义完整状态(背景、缩放、边框等)。JS 模拟
is-hovered类要避开三类坑如果交互依赖“悬停态维持”(比如下拉菜单、图片放大),不能只靠伪类。JS 模拟是最可控路径,但容易踩坑:
- 触摸穿透:快速连点多个元素,前一个
is-hovered类还没清除就被重复添加 - 滚动干扰:页面滚动时触发
touchstart,误判为点击,样式异常激活 - 内存泄漏:单页应用中组件反复挂载/卸载,没做事件清理就会累积监听器
真正难的从来不是怎么写
hover,而是怎么让它的边界在不同输入方式之间自然消失——它藏在pointer: fine的判定里,藏在touchcancel的监听里,也藏在元素是否被浏览器认定为“可交互”的上下文里。 - 必须带值:











