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


113

我正在编写一个打算同时在台式机和平板电脑上使用的网站。当从桌面上访问它时,我希望屏幕的可单击区域以各种:hover效果(不同的背景色等)亮起。使用平板电脑时,没有鼠标,所以我不希望任何悬停效果。

问题是,当我在平板电脑上点击某物时,浏览器显然具有某种“隐形鼠标光标”,它会移动到我点击的位置,然后将其留在那儿-所以我刚刚点击的东西会亮起悬停效果,直到我点击其他东西。

使用鼠标时如何获得悬停效果,而使用触摸屏时如何抑制它们?

万一有人在想建议,我不想使用用户代理嗅探。同一台设备可能同时具有触摸屏和鼠标(今天可能不那么普遍,但将来会越来越多)。我对设备不感兴趣,对当前使用方式不感兴趣:鼠标或触摸屏。

我已经尝试挂钩touchstarttouchmovetouchend事件,并呼吁preventDefault()对所有的人,这确实抑制“隐形鼠标”的一些时间; 但是,如果我在两个不同的元素之间快速来回轻按,轻按几下,它将开始移动“鼠标光标”并preventDefault始终照亮悬停效果-好像我并不总是很荣幸。除非有必要,否则我不会为您提供任何细节-我什至不确定这是否是正确的方法。如果有人有一个更简单的解决方法,我将不知所措。


编辑:可以使用bog-standard 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的鼠标事件。您可以使用它来查看点击事件也会触发mouseentermousemove并且mouseleave


7
@Blender,你读过这个问题吗?我已经解释了为什么用户代理嗅探是一个错误的选择。
乔·怀特

嗨,乔,您找到任何解决方案了吗?
Ismatjon 2014年

我正在寻找这种问题,很高兴找到了!
agpt

另请参阅此问题,该问题涉及在所有启用触摸的设备上(而不只是在移动设备上)禁用悬停:stackoverflow.com/questions/23885255/…–
Blade

Answers:


83

我从您的问题中得出的结论是,悬停效果会更改页面的内容。在这种情况下,我的建议是:

  • touchstart和上添加悬停效果mouseenter
  • 删除悬停的效果mouseleavetouchmoveclick

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

背景

为了模拟鼠标,如果用户在触摸屏(如iPad)上触摸并释放手指,则Webkit mobile等浏览器会触发以下事件(来源:html5rocks.com上的Touch And Mouse):

  1. touchstart
  2. touchmove
  3. touchend
  4. 300ms延迟,浏览器确保这是单击,而不是双击
  5. mouseover
  6. mouseenter
    • :如果一个mouseovermouseentermousemove事件改变页面的内容,以下事件从未被触发。
  7. mousemove
  8. mousedown
  9. mouseup
  10. click

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

更糟糕的是,如果将鼠标悬停事件更改页面内容,就不会触发click事件,如Safari Web内容指南-处理事件所述,尤其是“ 单指事件”中的图6.4 。确切的“内容更改”将取决于浏览器和版本。我发现,对于iOS 7.0,背景颜色的更改不是(或不再是?)内容更改。

解决方案说明

回顾一下:

  • touchstart和上添加悬停效果mouseenter
  • 删除悬停的效果mouseleavetouchmoveclick

请注意,没有作用touchend

这显然适用于鼠标事件:mouseentermouseleave(略有改善的版本mouseovermouseout)被解雇,并添加和删除悬停。

如果用户实际上click是一个链接,则悬停效果也将被删除。这样可以确保在用户按下Web浏览器中的“后退”按钮时将其删除。

这也适用于触摸事件:在touchstart上添加了悬停效果。在touchend上将其“不”删除。再次在上添加了它mouseenter,由于这不会引起内容更改(它已经添加了),因此click也会触发该事件,并且无需用户再次单击即可跟随该链接!

touchstart事件和click实际使用之间,浏览器具有300ms的延迟,因为悬停效果将在较短的时间内显示出来。

如果用户决定取消单击,则手指的移动将照常进行。通常,这是一个问题,因为不会mouseleave触发任何事件,并且悬停效果仍然存在。幸运的是,可以通过消除上的悬停效果来轻松解决此问题touchmove

而已!

请注意,例如可以使用FastClick库来消除300ms的延迟,但这在此问题之外。

替代解决方案

我发现以下替代方法存在以下问题:

  • 浏览器检测:极易出现错误。假定设备具有鼠标或触摸功能,而当触摸显示效果良好时,两者的结合将变得越来越普遍。
  • CSS媒体检测:我知道的唯一的仅CSS解决方案。仍然容易出现错误,并且仍然假定设备同时具有鼠标或触摸功能,而两者都有可能。
  • 在以下位置模拟点击事件touchend即使用户只想滚动或缩放,而实际上并没有点击链接的意图,这也会错误地跟随链接。
  • 使用变量来抑制鼠标事件:在其中设置一个变量,touchend该变量在以后的鼠标事件中用作if条件,以防止该时间点的状态更改。该变量在点击事件中重置。请参阅本页上的Walter Roman的答案。如果您确实不想在触摸界面上悬停效果,那么这是一个不错的解决方案。不幸的是,如果touchend由于其他原因触发了a 并且未触发点击事件(例如,用户滚动或缩放),并且随后试图用鼠标跟随链接(即在同时具有鼠标和触摸界面的设备上),则此方法不起作用)。

进一步阅读


1
好答案。我用你的小提琴来创建自己的解决方案。但是有时需要touchend。(当touchmove无法运行时-例如,当用户移开文档本身时...)
Agamemnus 2014年

伟大的答案,但在大多数情况下,我建议使用touchend 替代touchstart在大多数情况下,立即避免触发动作,当用户试图向上或滑动,向下滚动页面。仍会发生,但不太可能分散注意力或造成混淆(假设这是无害的,例如显示标签,而不是用户需要知道的事情)
user56reinstatemonica8 2013年

19

使用鼠标时如何获得悬停效果,而使用触摸屏时如何抑制它们?

也许您认为它不至于抑制触摸屏的悬停效果,而是增加鼠标事件的悬停效果?

如果要将:hover效果保留在CSS中,则可以为不同的媒体指定不同的样式:

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

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

不幸的是,除此以外,还有许多移动设备会忽略此操作,而只是使用屏幕规则。幸运的是,许多更新的移动/平板浏览器确实支持一些更高级的媒体查询:

@media screen and (max-width:800px) { /* non-hover styles here */ }

因此,即使忽略了“屏幕”或“手持式”部分,“最大宽度”也会帮您解决问题。您可以假设屏幕小于800像素的任何物体都必须是平板电脑或手机,并且不使用悬停效果。对于在低分辨率设备上使用鼠标的罕见用户,他们不会看到悬停效果,但否则您的站点会很好。

进一步阅读媒体查询?在线上有很多关于此的文章-这是其中之一:http : //www.alistapart.com/articles/return-of-the-mobile-stylesheet

如果将悬停效果从CSS中移出并应用JavaScript,则可以专门绑定到鼠标事件,和/或再次可以仅基于屏幕大小做出一些假设,最坏的情况是“问题”使用鼠标的用户会错过悬停效果。


我只想为鼠标事件添加悬停效果。但是触摸事件模仿鼠标事件。如果我钩住了触摸事件并调用preventDefault(),有时会抑制鼠标事件,但是正如我在问题中所说的那样,它并不可靠。
乔·怀特

6
至于媒体查询,当Windows 8出现并且PC同时具有触摸屏鼠标时会发生什么?如果用户将鼠标悬停,他们将看到鼠标光标,并且我想要悬停效果;如果他们用触摸屏点击,我不希望悬停效果。但是我还没有找到一种可靠的方法来检测触摸和鼠标之间的差异。
乔·怀特

好吧,一旦浏览器开发人员在使用触摸和鼠标的设备上拥有大量用户时,他们将更有动力使检测更加一致。但现在?我会根据屏幕尺寸进行猜测,但其他答案中还有其他建议。抱歉,我无济于事。
nnnnnn

9

我为一个最近的项目写了以下JS,这是一个台式机/手机/平板电脑网站,具有悬浮效果,不应出现在触摸屏上。

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

preventMouseover然后被设置回false每当mouseover事件被触发,这使得现场的工作,如果用户同时使用其触摸屏和鼠标作为预期。

我们知道这mouseovertouchstart由于在中声明它们的顺序而触发的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();

“每当触发mouseover事件时,preventMouseover都将重新设置为true,如果用户同时使用其触摸屏和鼠标,则该站点可以按预期工作。” -您的代码没有这样做。我想念什么吗?
Redtopia 2014年

@Redtopia感谢您的注意!我已经用一些遗漏的代码更新了答案。
Walter Roman

函数声明后不需要分号
nacholibre 2014年

@nacholibre已正确使用分号
Walter Roman

6

我本人确实想要一个纯粹的css解决方案,因为在我的所有视图周围撒上笨拙的javascript解决方案似乎是不愉快的选择。最后找到@ media.hover查询,它可以检测到“主要输入机制是否允许用户将鼠标悬停在元素上”。这样可以避免触摸设备,其中“悬停”更多是模拟动作,而不是输入设备的直接功能。

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

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

然后,我可以安全地将其设置为仅:hover当设备轻松支持此样式时css

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

尽管大多数现代浏览器都支持交互媒体功能查询,但某些流行的浏览器(如IE和Firefox)却不支持。就我而言,这很好,因为我只打算在台式机上支持Chrome,在移动设备上支持Chrome和Safari。


当您在Chrome devtools中模拟设备时,不错的解决方案似乎有效。但它仍不适用于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 2015年

2

解决相同问题的方法是进行功能检测(我使用类似下面的代码),查看是否已定义onTouchMove,如果是,则将CSS类“ touchMode”添加到主体,否则添加“ desktopMode”。

然后,每次某些样式效果仅适用于触摸设备,或仅适用于桌面,则css规则会附带相应的类:

.desktopMode .someClass:hover{ color: red }
.touchMode .mainDiv { width: 100%; margin: 0; /*etc.*/ }

编辑:当然,此策略会在CSS中添加一些额外的字符,因此,如果您担心CSS的大小,可以搜索touchMode和desktopMode定义,并将它们放入不同的文件中,以便可以为每个设备提供优化的CSS类型; 或者您可以在生产之前将类名更改为短得多的名称。


2

是的,我遇到了类似的问题,但是设法通过媒体查询和简单的CSS来解决。我确定我在这里违反了一些规则,但这对我有用。

我基本上必须接受某人制作的大量应用程序,并使其具有响应能力。他们使用jQueryUI,并要求我不要篡改任何jQuery,因此我只能使用CSS。

当我在触摸屏模式下按下他们的按钮之一时,悬停效果将在按钮的操作生效之前触发一秒钟。这是我固定的方法。

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

       #buttonOne{
            height: 44px;
        }


        #buttonOne:hover{
            display:none;
        }
}   

2

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

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;
}

在运行时Modernizrhtml像这样自动将css类添加到标签中

<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

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 '16

好吧,发布不能被视为良好实践的代码并没有真正的帮助。这就像将创可贴放在刀伤上。
NetOperator Wibby

1

iv找到了解决该问题的2个解决方案,这意味着您可以使用modernizr或其他工具检测触摸,并在html元素上设置一个触摸类。

这很好,但不能很好地支持

html.touch *:hover {
    all:unset!important;
}

但这有很好的支持

html.touch *:hover {
    pointer-events: none !important;
}

对我而言,它的工作无懈可击,它使所有的悬停效果都像在您触摸按钮时一样会亮起,但不会像最初的鼠标事件悬停一样出现bug。

我认为Modernizr从非接触式设备检测到触摸时表现最好:

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

编辑

我找到了一个更好,更简单的解决方案

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


0

听起来很奇怪,这可能有助于查看CSS。但是无论如何,如果正在发生并且一切都很好,则可以尝试将悬停效果转换为javascript(也可以使用jquery)。只需绑定到mouseover或更好的mouseenter事件,并在事件触发时点亮您的元素。

在此处查看最后一个示例:http : //api.jquery.com/mouseover/,您可以使用类似的方法在事件触发时记录日志并从那里获取!


CSS没什么特别的;看到我的编辑。我已经尝试了mouseenter; 没有骰子。轻击可移动“隐形鼠标光标”,因此它将触发mouseentermousemove(以及mouseleave在其他位置轻击)。
乔·怀特

0

如果您愿意使用JavaScript,则可以在页面中使用Modernizr。当页面加载时,非触摸屏浏览器将在html标签中添加类“ .no-touch”,但是对于触摸屏浏览器,html标签将在html标签中添加类“ .touch” 。

然后,这只是检查在决定添加mouseenter和mouseleave侦听器之前html标记是否具有no-touch类的一种情况。

if($('html').hasClass('no-touch')){
    $('.box').on("mouseenter", function(event){
            $(this).css('background-color','#0000ff')
    });
    $('.box').on("mouseleave", function(event){
            $(this).css('background-color','')
    });
}

对于触摸屏设备,事件将没有侦听器,因此在您点击时不会有悬停效果。


如果可以使用CSS完成此操作,请不要使用javascript(jquery),这是浪费计算机功能的原因
SimonDragsbæk2014年

1
更简单的是,您可以在CSS中为.touch和定义单独的规则.no-touch。要在CSS中与Modernizer一起重写您的答案,html.no-touch .box:hover {background-color: "#0000ff"}并且不为之定义任何东西,html.touch .box:hover应该可以解决问题,因为OP只是在尝试避免使用mobile :hover
沃尔特·罗曼

0

在最近完成的一个项目中,我使用jQuery的委托事件功能解决了此问题。它使用jQuery选择器查找某些元素,并在鼠标悬停在元素上时向这些元素添加/删除CSS类。据我测试,它似乎运行良好,其中包括运行Windows 8的可触摸笔记本电脑上的IE10。

$(document).ready(
    function()
    {
        // insert your own selector here: maybe '.hoverable'?
        var selector = 'button, .hotspot';

        $('body')
            .on('mouseover', selector, function(){ $(this).addClass('mouseover');    })
            .on('mouseout',  selector, function(){ $(this).removeClass('mouseover'); })
            .on('click',     selector, function(){ $(this).removeClass('mouseover'); });
    }
);

编辑:当然,此解决方案确实需要更改CSS才能删除“:hover”选择器,并预先考虑要“可悬浮”的元素。

但是,如果页面上有很多元素(例如几千个),它可能会变慢,因为此解决方案会捕获页面上所有元素上三种类型的事件,然后在选择器匹配时执行其操作。我将CSS类命名为“ mouseover”而不是“ hover”,因为我不希望任何CSS读者在写“ .hover”的地方阅读“:hover”。


0

这是我的解决方案:http : //jsfiddle.net/agamemnus/g56aw709/--下面的代码。

所有需要做的就是将其“:hover”转换为“ .hover” ...就是这样!此方法与其他方法之间的最大区别是,它也可以在非单一元素选择器(例如)上使用.my_class > *:hover {

handle_css_hover_effects ()

function handle_css_hover_effects (init) {
 var init = init || {}
 var handle_touch_events = init.handle_touch_events || true
 var handle_mouse_events = init.handle_mouse_events || true
 var hover_class         = init.hover_class         || "hover"
 var delay_preferences   = init.delay_preferences   || {touch: {add: 500, remove: 500}}
 function default_handler (curobj, input_type, op) {
  var hovered_element_selector = "*" + ((op == "add") ? ":" : ("." + hover_class))
  var hovered_elements = Array.prototype.slice.call(document.body.querySelectorAll(hovered_element_selector))
  var modified_list = []
  while (true) {
   if ((curobj == null) || (curobj == document.documentElement)) break
   if (hovered_elements.indexOf(curobj) != -1) modified_list.push (curobj)
   curobj = curobj.parentNode
  }
  function do_hover_change () {modified_list.forEach (function (curobj) {curobj.classList[op](hover_class)})}
  if ((!delay_preferences[input_type]) || (!delay_preferences[input_type][op])) {
   do_hover_change ()
  } else {
   setTimeout (do_hover_change, delay_preferences[input_type][op])
  }
 }

 if (handle_mouse_events) {
  document.body.addEventListener ('mouseover' , function (evt) {var curobj = evt.target; default_handler (curobj, "mouse", "add")})
  document.body.addEventListener ('mouseout'  , function (evt) {var curobj = evt.target; default_handler (curobj, "mouse", "remove")})
  document.body.addEventListener ('click'     , function (evt) {var curobj = evt.target; default_handler (curobj, "mouse", "remove")})
 }

 if (handle_touch_events) {
  document.body.addEventListener ('touchstart', function (evt) {var curobj = evt.target; default_handler (curobj, "touch", "add")})
  document.body.addEventListener ('touchend'  , function (evt) {var curobj = evt.target; default_handler (curobj, "touch", "remove")})
  document.body.addEventListener ('touchmove',  function (evt) {
   var curobj = evt.target
   var hovered_elements = Array.prototype.slice.call(document.body.querySelectorAll("*:hover"))
   var lastobj = null
   evt = evt.changedTouches[0]
   var elements_at_point = get_elements_at_point (evt.pageX, evt.pageY)
   // Get the last element that isn't at the current point but is still hovered over, and remove only its hover attribute.
   while (true) {
    if ((curobj == null) || (curobj == document.documentElement)) break
    if ((hovered_elements.indexOf(curobj) != -1) && (elements_at_point.indexOf(curobj) == -1)) lastobj = curobj
    curobj = curobj.parentNode
   }
   if (lastobj == null) return
   if ((!delay_preferences.touch) || (!delay_preferences.touch.remove)) {
    lastobj.classList.remove(hover_class)
   } else {
    setTimeout (function () {lastobj.classList.remove(hover_class)}, delay_preferences.touch.remove)
   }

   function get_elements_at_point (x, y) {
    var el_list = [], pe_list = []
    while (true) {
     var curobj = document.elementFromPoint(x, y)
     if ((curobj == null) || (curobj == document.documentElement)) break
     el_list.push (curobj); pe_list.push (curobj.style.pointerEvents)
     curobj.style.pointerEvents = "none"
    }
    el_list.forEach (function (current_element, i) {current_element.style.pointerEvents = pe_list[i]})
    return el_list
   }
  })
 }
}


0

您好,将来的朋友,您可能想使用pointer和/或hover媒体查询。该handheld媒体查询已被否决。

/* device is using a mouse or similar */
@media (pointer: fine) {
  a:hover {
    background: red;
  }
}

-1

尝试这个简单的2019 jquery解决方案,尽管已经有一段时间了;

  1. 将此插件添加到头部:

    src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"

  2. 将此添加到js:

    $("*").on("touchend", function(e) { $(this).focus(); }); //applies to all elements

  3. 建议的一些变化是:

    $(":input, :checkbox,").on("touchend", function(e) {(this).focus);}); //specify elements
    
    $("*").on("click, touchend", function(e) { $(this).focus(); });  //include click event`
    
    css: body { cursor: pointer; } //touch anywhere to end a focus`

笔记

  • 如果适用,将插件放在bootstrap.js之前,以避免影响工具提示
  • 仅在使用Safari或Chrome的iphone XR ios 12.1.12和ipad 3 ios 9.3.5上进行了测试。

参考文献:

https://code.jquery.com/ui/

https://api.jquery.com/category/selectors/jquery-selector-extensions/

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.