这是对 2015 年最初发布在 dev.opera 上的文章 的更新和大幅扩展版本。那篇文章参考了规范 媒体查询级别 4 的 2015 年 3 月 24 日编辑草案,并且对浏览器在实践中如何评估 any-hover:none
存在相当大的误解。
该规范此后已更新(包括我在原始文章发布后提交的澄清和示例),因此此更新版本删除了原始版本中的错误信息,并将解释与最新的工作草案保持一致。它还涵盖了与 JavaScript 触控/输入检测相关的其他方面。
媒体查询级别 4 交互媒体特性 — pointer
、hover
、any-pointer
和 any-hover
— 旨在允许网站根据用户输入设备的特定特性实现不同的样式和功能(无论是 CSS 特定的交互性,如 :hover
,还是使用 window.matchMedia
查询时的 JavaScript 行为)。
尽管规范仍处于工作草案阶段,但交互媒体特性通常 得到了良好的支持,不过,迄今为止,各个浏览器实现中仍然存在一些问题和不一致之处 — 请参阅最近的 pointer
/hover
/any-pointer
/any-hover
测试结果,其中包含对相关浏览器错误的引用。
交互媒体特性的常见用例通常是“根据用户是否使用触摸屏设备或使用鼠标/触控笔使控件更大/更小”和“仅在用户拥有允许基于悬停的交互的输入时才使用 CSS 下拉菜单”。
@media (pointer: fine) {
/* using a mouse or stylus - ok to use small buttons/controls */
}
@media (pointer: coarse) {
/* using touch - make buttons and other "touch targets" bigger */
}
@media (hover: hover) {
/* ok to use :hover-based menus */
}
@media (hover: none) {
/* don't use :hover-based menus */
}
开发人员还使用这些新的交互媒体特性作为实现基于标准的“触控检测”的一种方式,通常仅在识别设备具有粗略指针时侦听触控事件。
if (window.matchMedia && window.matchMedia("(pointer:coarse)").matches) {
/* if the pointer is coarse, listen to touch events */
target.addEventListener("touchstart", ...);
// ...
} else {
/* otherwise, listen to mouse and keyboard events */
// ...
}
但是,这些方法略显天真,源于对这些交互媒体查询旨在告诉我们的内容的误解。
什么是主要输入?
pointer
和 hover
的局限性之一在于,根据设计,它们仅公开浏览器认为是主要指针输入的特性。浏览器认为的和用户实际用作主要输入的内容可能不同 — 特别是在设备之间的界限以及它们支持的输入类型变得越来越模糊的今天。

一开始,值得注意的是,交互媒体特性仅涵盖指针输入(鼠标、触控笔、触摸屏)。它们不提供任何方法来检测用户的主要输入是否为键盘或类似键盘的界面,例如开关控制。理论上,对于键盘用户,浏览器可以报告 pointer: none
,表示用户的主要输入根本不是指针。但是,在实践中,没有浏览器为用户提供指定他们实际上是键盘用户的方法。因此请记住,无论交互媒体特性查询可能返回什么,都值得确保您的网站或应用程序也适用于键盘用户。
传统上,我们可以说手机或平板电脑的主要输入是触摸屏。但是,即使在这些设备上,用户也可能拥有其他输入,例如配对的蓝牙鼠标(多年来在 Android 上可用,现在在 iPadOS 中受支持,并且肯定会在 iOS 中推出),他们正在将其用作主要输入。


在这种情况下,虽然设备名义上具有 pointer: coarse
和 hover: none
,但用户实际上可能正在使用能够悬停的精细指针设备。类似地,如果用户拥有触控笔(如 Apple Pencil),其主要输入仍可能报告为触摸屏,但现在他们拥有一个可以提供精细指针精度的输入,而不是 pointer: coarse
。
在这些特定场景中,如果网站所做的只是使按钮和控件更大并避免基于悬停的交互,那么这对用户来说不会是主要问题:尽管使用了精细且能够悬停的鼠标或精细但仍然无法悬停的触控笔,他们将获得针对粗略、无法悬停的触摸屏的样式和功能。
如果网站使用 pointer: coarse
的提示进行更剧烈的更改,例如然后仅侦听触控事件,那么这对用户来说将成为问题 — 请参阅有关可能完全破坏体验的错误假设的部分。
但是,考虑相反的情况:“常规”台式机或笔记本电脑配有触摸屏,例如微软的 Surface。在大多数情况下,主要输入将是触控板/鼠标 — 具有 pointer:fine
和 hover:hover
— 但用户很可能正在使用触摸屏,它具有粗略的指针精度并且没有悬停功能。如果样式和功能随后专门针对触控板/鼠标的特性进行定制,用户可能会发现使用粗略、无法悬停的触摸屏存在问题或无法使用。
特性 | 触摸屏 | 触摸屏 + 鼠标 | 台式机/笔记本电脑 | 台式机/笔记本电脑 + 触摸屏 |
---|---|---|---|---|
pointer:coarse | true | true | false | false |
pointer:fine | false | false | true | true |
hover:none | true | true | false | false |
hover:hover | false | false | true | true |
有关此问题的类似看法,请参阅 Stu Cox 的 ”媒体查询级别 4 的优缺点”。虽然它指的是规范的更早版本,该版本仅包含 pointer
和 hover
以及这些特性必须报告能力最低而不是主要输入设备的要求。
最初的 pointer
和 hover
本身存在的问题在于,它们没有考虑多输入场景,并且依赖于浏览器能够正确选择单个主要输入。这就是 any-pointer
和 any-hover
发挥作用的地方。
测试所有输入的功能
any-pointer
和 any-hover
不仅关注主要指针输入,还报告所有可用指针输入的组合功能。
为了支持多输入场景,其中不同的(基于指针的)输入可能具有不同的特性,如果不同的输入设备具有不同的特性,则 any-pointer
(以及理论上的 any-hover
,但我们稍后会看到这方面毫无用处)的多个值可以匹配< (与仅引用主要指针输入的功能的 pointer 和 hover 相比)。在当前的实现中,这些媒体特性通常按如下方式评估
特性 | 触摸屏 | 触摸屏 + 鼠标 | 台式机/笔记本电脑 | 台式机/笔记本电脑 + 触摸屏 |
---|---|---|---|---|
any-pointer:coarse | true | true | false | true |
any-pointer:fine | false | true | true | true |
any-hover:none | true | false | false | false |
any-hover:hover | false | true | true | true |

pointer
和 hover
保持不变,但 any-pointer
和 any-hover
更改为涵盖新的能够悬停的 fine
输入。回到交互媒体特性的原始用例,我们可以根据任何可用指针输入的特性来做出决定,而不是仅仅根据主要指针输入的特性来决定提供更大或更小的输入或仅启用基于悬停的功能。粗略地翻译,我们不是说“如果主要输入具有 pointer: coarse
,则使所有控件更大”或“仅在主要输入具有 hover: hover
时才提供 CSS 菜单”,而是可以构建等同于说“如果任何指针输入为 coarse
,则使控件更大”和“仅在用户可用的至少一个指针输入能够悬停时才提供基于悬停的菜单”的媒体查询。
@media (any-pointer: coarse) {
/* at least one of the pointer inputs
is coarse, best to make buttons and
other "touch targets" bigger (using
the query "defensively" to target
the least capable input) */
}
@media (any-hover: hover) {
/* at least one of the inputs is
hover-capable, so it's at least
possible for users to trigger
hover-based menus */
}
由于 any-pointer
和 any-hover
的当前定义方式(作为“用户可用的所有指向设备的功能的并集”),any-pointer: none
仅在没有可用指针输入时才评估为 true,更重要的是,any-hover: none
仅在没有一个存在的指针输入能够悬停时才为 true。特别是对于后者,因此无法使用 any-hover: none
查询来确定是否存在一个或多个存在的指针输入无法悬停 — 我们只能使用此媒体特性查询来确定所有输入是否都无法悬停,这可以通过检查 any-hover: hover
是否评估为 false 来实现。这使得 any-hover: none
查询基本上是多余的。
我们可以通过推断,如果any-pointer: coarse
为真,则很可能是一个触摸屏,并且通常这些输入不支持悬停,但从概念上讲,我们在这里做出了假设,并且一旦出现一个也支持悬停的粗指针,这种逻辑就会失效。(对于那些怀疑我们是否会看到支持悬停的触摸屏的人,请记住,某些设备(如三星 Galaxy Note 和微软 Surface)具有支持悬停的手写笔,即使它没有接触数字化仪/屏幕也能检测到,因此某种形式的“悬停触摸”检测在未来并非不可能。)
组合查询以进行更合理的猜测
当然,any-pointer
和any-hover
提供的信息可以与pointer
和hover
以及浏览器对主要输入功能的判断相结合,以进行一些略微细致的评估。
@media (pointer: coarse) and (any-pointer: fine) {
/* the primary input is a touchscreen, but
there is also a fine input (a mouse or
perhaps stylus) present. Make the design
touch-first, mouse/stylus users can
still use this just fine (though it may
feel a big clunky for them?) */
}
@media (pointer: fine) and (any-pointer: coarse) {
/* the primary input is a mouse/stylus,
but there is also a touchscreen
present. May be safest to make
controls big, just in case users do
actually use the touchscreen? */
}
@media (hover: none) and (any-hover: hover) {
/* the primary input can't hover, but
the user has at least one other
input available that would let them
hover. Do you trust that the primary
input is in fact what the user is
more likely to use, and omit hover-
based interactions? Or treat hover
as something optional — can be
used (e.g. to provide shortcuts) to
users that do use the mouse, but
don't rely on it? */
}
动态变化
根据规范,浏览器应该响应用户环境的变化重新评估媒体查询。这意味着pointer
、hover
、any-pointer
和any-hover
交互式媒体功能可以在任何时候动态变化。例如,在移动/平板设备上添加/移除蓝牙鼠标将触发any-pointer
/any-hover
的变化。一个更极端的例子是 Surface 平板电脑,在其中添加/移除设备的“触控键盘”(包括键盘和触控板)将导致主要输入本身发生变化(当触控键盘存在时,从pointer: fine
/hover: hover
变为当 Surface 处于“平板电脑模式”时为pointer: coarse
/hover: none
)。

pointer:fine
、hover:hover
、any-pointer:coarse
、any-pointer:fine
和any-hover:hover
为真;一旦键盘盖被移除(并且 Windows 询问用户是否要切换到“平板电脑模式”),触摸将成为主要输入,pointer:coarse
和hover:none
,并且只有any-pointer:coarse
和any-hover:none
为真。如果您根据这些媒体功能修改网站的布局/功能,请注意,只要输入发生变化,网站可能会突然在“用户脚下”发生变化——不仅仅是在页面/网站首次加载时。
媒体查询可能不够——转向脚本
交互式媒体功能的基本缺点是,它们不一定能告诉我们现在正在使用的输入设备的任何信息。为此,我们可能需要深入研究一些解决方案,例如What Input?,它跟踪触发的特定 JavaScript 事件。但当然,这些解决方案只能在用户开始与网站交互之后提供有关用户输入的信息——此时,对布局或功能进行重大更改可能为时已晚。
请记住,即使是这些基于 JavaScript 的方法也可能同样容易导致错误的结果。在移动/平板电脑平台或辅助技术参与的情况下尤其如此,在这种情况下,通常会看到生成“伪造”的事件。例如,如果我们查看使用键盘和屏幕阅读器在桌面设备上激活控件时触发的事件序列,我们可以看到触发了伪造的鼠标事件。辅助技术这样做是因为,从历史上看,许多网页内容都被编码为适用于鼠标用户,但不一定适用于键盘用户,因此对于某些功能,模拟这些交互是必要的。
同样,在 iOS 的设置→辅助功能→键盘中激活“完全键盘支持”后,用户可以使用外部蓝牙键盘浏览网页内容,就像在桌面设备上一样。但是,如果我们查看移动/平板电脑设备和配对的键盘/鼠标的事件序列,这种情况会产生指针事件、触摸事件和后备鼠标事件——与触摸屏交互获得的序列相同。

在所有这些情况下,像 What Input?这样的脚本都会(可以理解地,并非由于其自身的原因)错误地识别当前的输入类型。
可能导致体验完全崩溃的错误假设
在概述了多输入设备的复杂性之后,现在应该清楚,仅侦听特定类型事件的方法(例如我们通常使用的“触摸检测”形式)很快就会失效。
if (window.matchMedia && window.matchMedia("(pointer: coarse)").matches) {
/* if the pointer is coarse, listen to touch events */
target.addEventListener("touchstart", ...);
// ...
} else {
/* otherwise, listen to mouse and keyboard events */
target.addEventListener("click", ...);
// ...
}
对于具有附加输入的“触摸”设备(例如带有外部鼠标的手机或平板电脑),此代码基本上会阻止用户使用触摸屏以外的任何内容。在主要由鼠标驱动的但确实具有辅助触摸屏界面的设备(如 Microsoft Surface)上,用户将无法使用其触摸屏。
不要将此视为“触摸或鼠标/键盘”,而是将其视为“触摸和鼠标/键盘”。如果出于性能原因,我们只想在存在实际触摸屏设备时注册触摸事件,我们可以尝试检测any-pointer: coarse
。但是,我们还应该为鼠标和键盘保留其他常规事件侦听器。
/* always, as a matter of course, listen to mouse and keyboard events */
target.addEventListener("click", ...);
// ...
if (window.matchMedia && window.matchMedia("(any-pointer: coarse)").matches) {
/* if there's a coarse pointer, *also* listen to touch events */
target.addEventListener("touchstart", ...);
// ...
}
或者,我们可以通过使用指针事件来避免关于不同事件类型的整个难题,指针事件以单个统一的事件模型涵盖所有类型的指针输入,并且得到了相当广泛的支持。
让用户做出明确的选择
巧妙规避我们无法对用户正在使用的输入类型做出绝对判断的一种潜在解决方案可能是,使用媒体查询和 What Input?等工具提供的信息,而不是立即在不同的布局/功能之间切换——或者更糟糕的是,仅侦听特定类型的事件,并可能阻止任何其他输入类型——而是仅将它们用作信号,指示何时为用户提供明确的切换模式的方法。
例如,请参阅 Microsoft Office 允许您在“触摸”和“鼠标”模式之间切换的方式。在触摸设备上,此选项默认显示在应用程序的工具栏中,而在非触摸设备上,它最初是隐藏的(尽管无论是否存在触摸屏,都可以启用它)。

网站或 Web 应用程序可以采用相同的方法,甚至可以根据主要输入设置默认值——但仍然允许用户显式更改模式。并且,使用类似于 What Input?的方法,网站可以检测触摸输入的首次出现,并在用户想要切换到触摸友好模式时向其发出警报/提示。
错误假设的可能性——负责任地使用查询
使用媒体查询级别 4 交互式媒体功能并根据可用主要或附加指针输入的特性调整我们的网站是一个好主意——但要注意对这些媒体功能实际含义的错误假设。与类似的功能检测方法一样,开发人员需要了解他们究竟要检测什么、特定检测的局限性,最重要的是,考虑他们为什么要这样做——类似于我在关于检测触摸的文章中概述的问题。
pointer
和hover
告诉我们浏览器确定为主要设备输入的任何内容的功能。any-pointer
和any-hover
告诉您所有连接的输入的功能,并结合有关主要指针输入的信息,它们使我们能够对用户的特定设备/场景做出合理的猜测。我们可以使用这些功能来告知我们的布局或我们想要提供的交互/功能类型;但不要忽视这些假设可能不正确的可能性。媒体查询本身不一定有缺陷(尽管大多数浏览器似乎仍然存在一些特性和错误,这增加了潜在的问题)。这仅仅取决于它们的使用方式。
有了这些,我想总结一下,并提供一些建议来“防御”输入检测的陷阱。
❌ **不要**…
假设只有一种输入类型。如今不再是“触摸*或*鼠标/键盘”,而是“触摸*和*鼠标/键盘”——并且可用的输入类型可能会随时更改,甚至在初始页面加载后也会更改。
只使用pointer
和hover
。“主要”指针输入不一定就是用户正在使用的输入。
通常依赖hover
。无论hover
或any-hover
建议什么,您的用户可能正在使用一个不支持悬停的指针输入,并且您目前无法检测到这一点,除非它是主要输入(因为如果*该*特定输入缺少悬停,则hover: none
为真,但如果*没有*输入支持悬停,则any-hover: none
始终为真)。并且请记住,基于悬停的界面通常不适用于键盘用户。
✅ 请做…
使您的界面“触控友好”。如果您检测到存在any-pointer:coarse
输入(很可能是触摸屏),请考虑提供较大的触摸目标并在它们之间留出足够的间距。即使用户此时正在使用其他输入(如鼠标),也不会造成任何损害。
让用户选择。如果所有其他方法都失败了,请考虑为用户提供一个选项/切换按钮,以便在触摸或鼠标布局之间切换。您可以随意使用从媒体查询中收集到的任何信息(例如any-pointer: coarse
为真),以便对切换按钮的初始设置进行合理的猜测。
记住键盘用户。无论用户是否正在使用任何指针输入,都不要忘记键盘可访问性——它无法被最终确定地检测到,因此只需确保您的内容在默认情况下适用于键盘用户。
这正是我一直在寻找的!你的文章非常清晰!我通过CSS-Tricks的文章不断学习和了解很多东西!谢谢
好文章!
一个有趣的后续跟进:带有Apple Pencil的iPadOS现在支持悬停https://webkit.org/blog/13399/webkit-features-in-safari-16-1/#hover-on-ipados-with-apple-pencil – 类似于某些Android手机/平板电脑以及像Microsoft Surface或Wacom数位板等设备上带有悬停/倾斜功能的触控笔的支持。