@media (hover: none) 才是判断触控模式的关键,它匹配无悬停能力的设备(如iPhone、iPad默认状态),由浏览器根据实时输入能力决定,不依赖UA或尺寸,且支持主流现代浏览器。

不能用 @media (max-width: 768px) 或 navigator.userAgent 判断“是不是移动设备”,它们和输入方式无关;真正能区分触摸操作与鼠标悬停的,是 hover 和 pointer 这两个媒体特性。
为什么 @media (hover: none) 才是判断触控模式的关键
这个查询匹配的是「当前没有 hover 能力」的设备,包括 iPhone、iPad(默认)、绝大多数 Android 手机和平板。它不依赖屏幕尺寸或 UA 字符串,而是在渲染前就由浏览器根据输入能力决定,Chrome 51+、Firefox 62+、Safari 14.1+ 全部支持。
- 写
@media (hover: hover)是为了捕获鼠标/触控笔等支持悬停的场景,不是“PC专属”——Surface Pro 插着鼠标时也匹配它 -
@media (hover: none)不等于“是手机”,而是“此刻无法 hover”,比如 iPad 连蓝牙鼠标后,这个媒体查询会自动失效 - 别用
'ontouchstart' in window驱动 CSS:桌面 Chrome 开发者工具模拟器、某些 Linux 浏览器也会返回true,但它只说明事件注册了,不代表物理触摸可用
@media (pointer: coarse) 适合调整交互密度,但不是设备开关
pointer: coarse 表示当前主指针是粗粒度(如手指),pointer: fine 表示精细(如鼠标、触控笔)。它的值会随用户切换输入方式实时变化,CSS 会重算,但 JS 不会自动感知。
- 适合场景:把点击区域设为
min-height: 44px、禁用:hover动画、把图标 hover 态换成 tap 态 - 不适合场景:当作“是否是手机”的布尔开关——Windows 触控笔记本、游戏手柄、甚至电视遥控器也可能触发
coarse - 单独用
pointer: coarse有风险:Surface 插鼠标后,它可能从coarse切回fine,但你的样式不会自动降级补救
安全组合:用 (hover: hover) and (pointer: fine) 控制悬停菜单
这是防止触摸屏误触下拉菜单的核心写法。只对同时满足“支持悬停”和“指针精细”的设备启用 :hover 展开逻辑,绕过所有手指操作场景。
立即学习“前端免费学习笔记(深入)”;
- 错误写法:
nav ul li:hover > ul { display: block; }—— 在 iOS Safari 上会导致点击后菜单卡住 - 正确写法:
@media (hover: hover) and (pointer: fine) { nav ul li:hover > ul { display: block; } } - 必须搭配 JS 降级:首次触摸后,监听
touchstart并给<html>加data-touch-active类,手动控制子菜单显隐
动态响应输入方式切换需要 JavaScript 辅助
CSS 媒体查询在页面加载时求值,不监听运行时输入变更。用户中途插拔鼠标、切换键盘/触控模式时,需靠 JS 捕获变化并同步状态。
- 用
window.matchMedia('(hover: none)')获取初始值,再监听change事件 - 示例:
const hoverQuery = window.matchMedia('(hover: none)'); hoverQuery.addEventListener('change', e => { document.documentElement.dataset.hover = e.matches ? 'none' : 'hover'; }); - 注意:不要仅靠一次
touchstart就永久标记“是触屏”,有些设备(如带触控的 Mac)可能混合使用,应持续观察输入特性变化
最易被忽略的一点:CSS 媒体查询只能回答“当前输入能力是什么”,不能回答“用户偏好怎么交互”。如果用户开着辅助功能、偏好减少动画、或正在用遥控器操作,hover 和 pointer 可能都不够用,得结合 prefers-reduced-motion 和运行时事件流做兜底。


















