禁用移动浏览器上的悬停效果

123
我正在开发一个网站,旨在供台式机和平板电脑使用。当从台式机访问时,我希望屏幕上的可点击区域会呈现出:hover效果(不同的背景颜色等)。但是对于平板电脑,则没有鼠标,所以我不想有任何:hover效果。
问题在于,当我在平板电脑上轻触某个东西时,浏览器显然有某种“隐形鼠标光标”,它将光标移动到我轻触的位置,然后把它留在那里——所以我刚才轻触的那个东西会一直呈现:hover效果,直到我轻触其他东西。
如何在使用鼠标时获得:hover效果,但在使用触摸屏时抑制它们?
如果有人在想建议,我不想使用用户代理嗅探。同一设备可能同时拥有触摸屏和鼠标(也许在今天不太常见,但在未来就不是这样了)。我不关心设备,我关心的是它当前的使用方式:鼠标还是触摸屏。
我已经尝试钩取touchstart、touchmove和touchend事件,并在所有这些事件上调用preventDefault(),有时可以阻止“隐形鼠标光标”,但如果我在两个不同的元素之间快速切换轻触几次,它会开始移动“鼠标光标”,并仍然呈现:hover效果——就像我的preventDefault并不总是被接受。除非必要,否则我不会为细节烦扰您——我甚至不确定这是否是正确的方法;如果有更简单的解决方案,我听取意见。
编辑:可以使用标准CSS :hover进行重现,但是这里提供一个快速重现方式供参考。
<style>
  .box { border: 1px solid black; width: 150px; height: 150px; }
  .box:hover { background: blue; }
</style>
<div class="box"></div>
<div class="box"></div>

如果你将鼠标悬停在任何一个框上,它就会变成蓝色背景,这是我想要的效果。但如果你点击其中任何一个框,它也会变成蓝色背景,这是我想要防止的事情。

我还发布了一个示例在此处,其中执行了上述操作并连接了jQuery的鼠标事件。你可以使用它来查看触摸事件也会触发mouseentermousemovemouseleave


7
@Blender,你看过问题了吗?我已经解释了为什么用户代理检测是一个不好的选择。 - Joe White
嘿,乔,你找到解决方案了吗? - Ismatjon
我一直在寻找这样的问题,很高兴终于找到了! - Aman Gupta
请参考以下问题,该问题涉及禁用任何触摸设备上的悬停效果,而不仅仅是移动设备:https://dev59.com/qWAg5IYBdhLWcg3wM4db - blade
21个回答

88

从您的问题中我了解到您的悬停效果会改变页面内容。在这种情况下,我的建议是:

  • touchstartmouseenter上添加悬停效果。
  • mouseleavetouchmoveclick上删除悬停效果。

或者,您可以编辑您的页面,使其没有内容更改。

背景

为了模拟鼠标,像Webkit mobile这样的浏览器会在用户触摸并释放触摸屏幕(如iPad)时触发以下事件(来源:Touch And Mouse on html5rocks.com):

  1. touchstart
  2. touchmove
  3. touchend
  4. 300ms延迟,浏览器确保这是单击而不是双击
  5. mouseover
  6. mouseenter
    • 注意:如果mouseovermouseentermousemove事件更改页面内容,则永远不会触发以下事件。
  7. mousemove
  8. mousedown
  9. mouseup
  10. click

似乎不可能仅仅告诉Web浏览器跳过鼠标事件。

更糟糕的是,如果鼠标悬停事件更改了页面内容,则永远不会触发点击事件,如Safari Web Content Guide-处理事件上所解释的那样,特别是在One-Finger Events中的6.4图。什么是“内容更改”将取决于浏览器和版本。我发现,在iOS 7.0中,背景颜色的更改不是(或不再是)内容更改。
解决方案说明:
- 在 touchstartmouseenter上添加悬停效果。 - 在 mouseleavetouchmoveclick 上删除悬停效果。 - 注意,touchend上没有任何操作!
对于鼠标事件,这显然有效:会触发 mouseentermouseleave(稍微改进了 mouseovermouseout),并添加和删除悬停效果。
如果用户实际上点击链接,悬停效果也会被移除。这确保了如果用户在Web浏览器中按下返回按钮,则会将其删除。
这对于触摸事件也有效:在touchstart上添加悬停效果。它在touchend上不被移除。它再次添加到 mouseenter 上,由于这不会导致内容更改(已经添加了),因此click事件也会触发,并且无需用户再次点击即可跟随链接!
浏览器在 touchstart 事件和 click 之间有300毫秒的延迟,实际上是很好地利用了这段时间,因为悬停效果将在此短时间内显示。

如果用户决定取消点击,则手指的移动将正常执行。通常,这是一个问题,因为没有触发mouseleave事件,悬停效果仍然存在。但值得庆幸的是,通过在touchmove上删除悬停效果可以轻松解决这个问题。

就这样!

请注意,可以使用FastClick库等方式消除300毫秒的延迟,但这超出了此问题的范围。

替代方案

我对以下替代方案发现了以下问题:

  • 浏览器检测:极易出错。假定设备具有鼠标或触摸屏,而鼠标和触摸屏的组合将越来越普遍。
  • CSS媒体检测:这是我知道的唯一基于CSS的解决方案。仍然容易出错,并且仍然假定设备具有鼠标或触摸屏,而两者都可能存在。
  • touchend中模拟点击事件:这将错误地跟踪链接,即使用户只想滚动或缩放,而没有实际点击链接的意图。
  • 使用变量抑制鼠标事件:touchend中设置一个变量,该变量用作后续鼠标事件的if条件,在那个时间点阻止状态更改。单击事件中将重置该变量。如果您真的不想在触摸界面上出现悬停效果,则这是一个不错的解决方案。不幸的是,如果由于其他原因引发了touchend并且没有触发单击事件(例如,用户滚动或缩放),则此方法无法解决问题,并且随后尝试使用鼠标跟踪链接(即在具有鼠标和触摸界面的设备上)。

进一步阅读


1
很好的答案。我使用了你的fiddle来开始创建自己的解决方案。有时候需要touchend(当touchmove不运行时--即当用户移动到文档本身之外...)。 - Agamemnus
非常好的答案,但在大多数情况下,我建议使用touchend而不是touchstart,以避免在用户尝试向上或向下滑动页面时立即触发操作。这种情况仍然会发生,但不太可能分散或混淆注意力(假设它是一些无害的东西,比如显示标签,而不是用户需要知道发生了什么)。 - user56reinstatemonica8

19

如何在使用鼠标时获取悬停效果,但在触摸屏上抑制它们?

也许不要过多考虑在触摸屏上抑制悬停效果,而是将其视为添加鼠标事件的悬停效果?

如果你想保留CSS中的`:hover`效果,你可以为不同的媒体指定不同的样式:

@media screen { /* hover styles here */ } 

@media handheld { /* non-hover styles here */ }

不幸的是,许多移动设备都忽略了这一点,而只使用屏幕规则。但是,很多新的移动/平板电脑浏览器确实支持一些更高级的媒体查询:

@media screen and (max-width:800px) { /* non-hover styles here */ }
即使忽略“屏幕”或“手持设备”的部分,“max-width”也能为您解决问题。您可以假定任何屏幕小于800像素的设备都是平板电脑或手机,并且不使用悬停效果。对于在低分辨率设备上使用鼠标的少数用户,他们将看不到悬停效果,但您的网站在其他方面仍可正常使用。
想要了解更多关于媒体查询的内容?网上有很多文章 - 这里是其中之一:http://www.alistapart.com/articles/return-of-the-mobile-stylesheet 如果将悬停效果从CSS中移出并使用JavaScript应用它们,那么您可以专门绑定鼠标事件,并且/或者基于屏幕尺寸进行某些假设,最坏情况下的“问题”是一些使用鼠标的用户会错过悬停效果。

我想为鼠标事件添加悬停效果。但是触摸事件模拟鼠标事件。如果我挂钩触摸事件并调用preventDefault(),有时会抑制鼠标事件,但正如我在问题中所说,这不是可靠的。 - Joe White
6
关于媒体查询,当Windows 8推出并且PC上同时有触摸屏和鼠标时会发生什么呢?如果用户使用鼠标悬停,他们将看到鼠标指针,并希望看到悬停效果;如果他们使用触摸屏轻触,则不希望看到悬停效果。但我还没有找到可靠的方法来区分触摸和鼠标。 - Joe White
也许当浏览器开发者在触摸和鼠标设备上拥有更多用户时,他们会更有动力使侦测更加一致。但现在呢?我会根据屏幕大小进行猜测,但其他回答中也有其他建议。很抱歉无法提供更多帮助。 - nnnnnn
大家好,来自2023年的问候。以防有人晚些时候看到这条消息,现在你可以使用媒体查询(hover:hover)来应用当鼠标悬停被支持时的悬停样式。 - kjetilh

10

我为最近的一个项目编写了以下JS代码,这是一个桌面/移动/平板网站,拥有不应在触摸上出现的悬停效果。

下面的mobileNoHoverState模块具有变量preventMouseover(最初声明为false),该变量在用户在元素$target上触发touchstart事件时设置为true

每当触发mouseover事件时,preventMouseover就会被设置回false,这使得如果用户同时使用触摸屏和鼠标,则网站可以按预期工作。

我们知道mouseover是在init中声明的顺序之后触发的,因此可以确认这一点。

var mobileNoHoverState = function() {

    var hoverClass = 'hover',
        $target = $(".foo"), 
        preventMouseover = false;

    function forTouchstart() {
        preventMouseover = true;
    }

    function forMouseover() {
        if (preventMouseover === false) {
            $(this).addClass(hoverClass);
        } else {
            preventMouseover = false;
        }
    }

    function forMouseout() {
        $(this).removeClass(hoverClass);
    }

    function init() {
        $target.on({
            touchstart  : forTouchstart,
            mouseover   : forMouseover,
            mouseout    : forMouseout
        });                
    }

    return {
        init: init
    };
}();

该模块随后在代码的较低位置进行实例化:

mobileNoHoverState.init();

“preventMouseover” 在鼠标悬停事件被触发后会被重新设置为 true,这样如果用户同时使用触摸屏和鼠标,网站就可以按预期工作。你的代码没有做到这一点,我有什么遗漏吗? - Redtopia
@Redtopia 谢谢你提醒我!我已经更新了答案,加上了遗漏的代码。 - Walter Roman
函数声明后不需要分号。 - nacholibre
@nacholibre 正确使用了分号。 - Walter Roman

6

我真的很希望自己能找到一种纯css解决方案,因为在所有视图中撒上一个笨重的javascript解决方案似乎是一个不愉快的选择。最终发现了@media.hover查询,它可以检测“主要输入机制是否允许用户悬停在元素上”。这避免了触摸设备上的“悬停”更多地是模拟操作而不是输入设备的直接功能。

例如,如果我有一个链接:

<a href="/" class="link">Home</a>

那么我可以使用这个 css 安全地将其样式设置为只有在设备轻松支持时才出现 :hover

@media (hover: hover) {
  .link:hover { /* hover styles */ }
}

虽然大多数现代浏览器都支持交互媒体特性查询,但一些流行的浏览器如IE和Firefox不支持。在我的情况下,这很好,因为我只打算支持桌面版Chrome和移动版Chrome和Safari。


很好的解决方案,当您在Chrome开发工具中模拟设备时似乎可以工作。但是对于Chrome Android仍然无法正常工作 :-( - Sjeiti
我认为这是最方便和正确的解决方案。希望它能尽快被添加到W3C标准中。 - GProst

5
我的解决方案是将 hover-active 的 CSS 类添加到 HTML 标签上,并在所有 CSS 选择器的开始处使用 :hover,然后在第一次 touchstart 事件发生时移除该类。 http://codepen.io/Bnaya/pen/EoJlb JS:
(function () {
    'use strict';

    if (!('addEventListener' in window)) {
        return;
    }

    var htmlElement = document.querySelector('html');

    function touchStart () {
        document.querySelector('html').classList.remove('hover-active');

        htmlElement.removeEventListener('touchstart', touchStart);
    }

    htmlElement.addEventListener('touchstart', touchStart);
}());

HTML:

<html class="hover-active">

CSS:

.hover-active .mybutton:hover {
    box-shadow: 1px 1px 1px #000;
}

不必监听触摸事件,你可以测试'ontouchstart' in window。例如:if ('ontouchstart' in window) document.querySelector('html').classList.remove('hover-active'); - Yuval A.
@YuvalA。我等待触摸事件的原因是有些设备支持指针和触摸,如果用户使用指针,我不想移除悬停状态。用户可能会先使用触摸再使用指针,但我对此无能为力。 - Bnaya

2

没错,我之前也遇到过类似的问题,但通过媒体查询和简单的CSS做法解决了。虽然可能违反了一些规则,但对我来说已经起作用了。

我需要将某人制作的大型应用变成响应式,并且他们使用了jQueryUI,要求我不要更改它们的任何jQuery代码,因此我只能使用CSS。

在触摸屏模式下按下其中一个按钮时,悬停效果会在按钮执行动作前持续1秒钟。以下是我解决的方法。

@media only screen and (max-width:1024px) {

       #buttonOne{
            height: 44px;
        }


        #buttonOne:hover{
            display:none;
        }
}   

2
我为解决这个问题所做的是添加一个功能检测(我使用类似这样的代码),查看是否定义了onTouchMove,如果是,则将CSS类“touchMode”添加到body中;否则将“desktopMode”添加到body中。然后,每次某些样式效果只适用于触摸设备或只适用于桌面时,CSS规则都会以相应的类为前缀。请注意保留HTML标签。
.desktopMode .someClass:hover{ color: red }
.touchMode .mainDiv { width: 100%; margin: 0; /*etc.*/ }

编辑:当然,这种策略会在你的CSS中添加一些额外的字符,所以如果你关心CSS大小,你可以搜索touchMode和desktopMode定义,并将它们放入不同的文件中,这样你就可以为每种设备类型提供优化的CSS;或者你可以在进入生产环境之前将类名更改为更短的名称。


2

在我的项目中,我们使用了https://www.npmjs.com/package/postcss-hover-prefixhttps://modernizr.com/来解决这个问题。 首先,我们使用postcss-hover-prefix对输出的CSS文件进行后处理。它为所有的CSS hover规则添加了.no-touch

const fs = require("fs");
const postcss = require("postcss");
const hoverPrfx = require("postcss-hover-prefix");

var css = fs.readFileSync(cssFileName, "utf8").toString();
postcss()
   .use(hoverPrfx("no-touch"))
   .process(css)
   .then((result) => {
      fs.writeFileSync(cssFileName, result);
   });

css

a.text-primary:hover {
  color: #62686d;
}

变成

.no-touch a.text-primary:hover {
  color: #62686d;
}

在运行时,Modernizr 会自动将 CSS 类添加到 html 标签中,如下所示:

<html class="wpfe-full-height js flexbox flexboxlegacy canvas canvastext webgl 
  no-touch 
  geolocation postmessage websqldatabase indexeddb hashchange
  history draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage
  borderradius boxshadow textshadow opacity cssanimations csscolumns cssgradients
  cssreflections csstransforms csstransforms3d csstransitions fontface
  generatedcontent video audio localstorage sessionstorage webworkers
  applicationcache svg inlinesvg smil svgclippaths websocketsbinary">

这样的CSS后处理加上Modernizr可以禁用触摸设备的悬停效果,并为其他设备启用。事实上,这种方法受到Bootstrap 4的启发,他们解决了同样的问题:https://v4-alpha.getbootstrap.com/getting-started/browsers-devices/#sticky-hoverfocus-on-mobile


1
我已经找到了两种解决方案来解决这个问题,其中暗示您可以使用Modernizr或其他工具检测触摸,并在HTML元素上设置一个触摸类。
这很好,但是不太支持得很好:
html.touch *:hover {
    all:unset!important;
}

但是这有一个非常好的support
html.touch *:hover {
    pointer-events: none !important;
}

对我来说运作得非常顺畅,它使所有的悬停效果都像当你触摸按钮时它会亮起,但不会像鼠标事件的初始悬停效果一样出现错误。

我认为Modernizr在检测无触摸设备方面做得最好:

https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js

编辑

我找到了一个更好、更简单的解决方法来解决这个问题。

如何确定客户端是触摸设备


1
你可以在触摸屏上触碰元素时触发mouseLeave事件。以下是适用于所有<a>标签的解决方案:
function removeHover() {
    var anchors = document.getElementsByTagName('a');
    for(i=0; i<anchors.length; i++) {
        anchors[i].addEventListener('touchstart', function(e){
            $('a').mouseleave();
        }, false);
    }
}

JSHint 说“不要在循环内部创建函数”。 - NetOperator Wibby
这只是一个 hack。它不能被视为良好实践的可重用代码。@NetOperatorWibby - Said Kholov
发布不能被视为良好实践的代码并不真正有帮助。这就像在刀伤上贴上创口贴一样。 - NetOperator Wibby

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接