在移动浏览器上禁用hover效果

我正在编写一个网站,旨在从桌面和平板电脑中使用。 从桌面访问时,我希望屏幕上的可点击区域具有以下特性:hover效果(不同背景颜色等)使用平板电脑时,没有鼠标,因此我不希望有任何悬浮效果。

问题是,当我点击平板电脑上的东西,浏览器显然有一种“隐形鼠标光标”,它移动到我点击的位置,然后离开它 – 所以我刚刚点击的东西点亮了一个hover效果,直到我点击别的东西。

当我使用鼠标时,如何获得hover效果,但在使用触摸屏时会将其抑制?

如果有人想提出这个build议,我不想使用用户代理嗅探。 相同的设备可以同时具有触摸屏和鼠标(今天可能不那么常见,但在将来会更加如此)。 我对该设备不感兴趣,我对如何使用鼠标或触摸屏感兴趣。

我已经尝试过hook touchstarttouchmovetouchend事件,并且在所有这些事件上调用preventDefault() ,这样做在一定程度上抑制了“隐形鼠标光标”。 但是如果我在两个不同的元素之间来回切换,几次点击之后,它就会开始移动“鼠标指针”并照亮hover效果 – 就像我的preventDefault并不总是被尊重。 除非有必要,否则我不会对您有任何疑问 – 我甚至不知道这是正确的做法。 如果任何人有一个更简单的修复,我都耳朵。


编辑:这可以用bog标准的CSS :hover ,但这里有一个快速repro作为参考。

 <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

我从你的问题来看,你的hover效果改变了你的页面的内容。 在这种情况下,我的build议是:

  • touchstarttouchstart上添加hover效果。
  • 移除mouseleavetouchmoveclick上的hover效果。

或者,你可以编辑你的页面,没有内容的变化。

背景

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

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

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

更糟糕的是,如果一个mouseover事件改变了页面内容,那么点击事件就永远不会被触发,正如Safari Web内容指南 – 处理事件所解释的那样,特别是单指事件中的图6.4。 究竟什么是“内容改变”,将取决于浏览器和版本。 我发现对于iOS 7.0,背景颜色的改变不是(或者不再是)一个内容改变。

解决scheme

回顾一下:

  • touchstarttouchstart上添加hover效果。
  • 移除mouseleavetouchmoveclick上的hover效果。

请注意,在touchend上没有任何操作!

这显然适用于鼠标事件: mouseentermouseleave (略有改进的mouseovermouseout版本)被触发,并添加和移除hover。

如果用户实际上click一个链接,hover效果也被删除。 这确保如果用户在Web浏览器中按下后退button,它将被删除。

这也适用于触摸事件:在touchstart添加hover效果。 这是'''不'''删除touchend。 它会在mouseenter再次mouseenter ,并且由于这不会导致内容更改(已经添加),所以click事件也会被触发,并且链接被执行,而不需要用户再次点击!

touchstart事件和click之间的浏览器之间的300毫秒延迟实际上是很好用的,因为在这段时间内会显示hover效果。

如果用户决定取消点击,则手指的移动将如常进行。 通常情况下,这是一个问题,因为没有mouseleave事件被触发,hover效果依然存在。 谢天谢地,这可以通过删除touchmove上的hover效果来touchmove

而已!

请注意,可以删除300ms的延迟,例如使用FastClick库 ,但这个问题超出了范围。

替代scheme

我发现下列替代scheme存在以下问题:

  • 浏览器检测:非常容易出错。 假设设备具有鼠标或触摸,而当触摸显示器增加时,两者的组合将变得越来越普遍。
  • CSS媒体检测:我知道的唯一的仅CSS的解决scheme。 仍然容易出错,并且仍然假定设备具有鼠标或触摸,而两者都是可能的。
  • 模拟touchend的点击事件:即使用户只想滚动或缩放,但不会实际点击链接,这将错误地跟随链接。
  • 使用variables来抑制鼠标事件:这在touchend中设置了一个variables,在随后的鼠标事件中用作if条件来防止在那个时间点的状态变化。 该variables在点击事件中被重置。 请参阅Walter Roman在此页上的回答。 如果你不想在触摸界面上产生hover效果,这是一个体面的解决scheme。 不幸的是,如果由于其他原因触发touchend并且没有触发点击事件(例如,用户滚动或缩放),并且随后尝试用鼠标跟随链接(即,在具有鼠标和触摸的设备上),则这不起作用接口)。

进一步阅读

当我使用鼠标时,如何获得hover效果,但在使用触摸屏时会将其抑制?

也许不要认为这是为了抑制触摸屏的hover效果,而是为鼠标事件添加hover效果?

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

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

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

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

所以,即使“屏幕”或“手持”部分被忽略,“最大宽度”将为你做的伎俩。 你可以假设屏幕小于800像素的任何东西都必须是平板电脑或手机,而不是使用hover效果。 对于在低分辨率设备上使用鼠标的罕见用户,他们不会看到hover效果,但是您的网站会很好。

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

如果您将hover效果转移出您的CSS,并将其应用于JavaScript,那么您可以专门绑定到鼠标事件,和/或再次,你可以做一些假设,只是基于屏幕大小与最坏的情况下,“问题”使用鼠标的用户错过了hover效果。

我为最近的一个项目编写了以下JS,这个项目是一个桌面/手机/平板电脑网站,hover效果不应该出现在触摸屏上。

下面的mobileNoHoverState模块有一个variablespreventMouseover (最初声明为false ),当用户在元素$target上触发touchstart事件时,该variables设置为true

mouseover事件触发时, preventMouseover将被设置为false ,这样,如果用户同时使用触摸屏和鼠标,则可以按照预期工作。

我们知道在touchstart之后,由于它们在init中被声明的顺序, mouseover被触发。

 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(); 

我的解决scheme是将hover活动的CSS类添加到HTML标记,并在所有的CSSselect器的开始使用它: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; } 

我真的很想要一个纯粹的css解决scheme,因为在我的所有视图中散布一个重要的JavaScript解决scheme似乎是一个不愉快的select。 最后find@ media.hover查询,它可以检测到“主要input机制是否允许用户将鼠标hover在元素上”。 这避免了触摸设备的“hover”比input设备的直接能力更仿真动作。

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

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

然后,我可以安全地将其设置为:hover当设备轻松支持这个css :hover

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

虽然大多数现代浏览器都支持交互媒体function查询,但是一些stream行的浏览器(如IE和Firefox)则不支持。 在我的情况下,这工作得很好,因为我只打算在桌面上支持Chrome,在手机上支持Chrome和Safari。

我已经做了解决同样的问题是有一个function检测(我使用类似这样的代码 ),看是否定义了onTouchMove,如果是这样我添加css类“touchMode”的身体,否则我加“ desktopMode”。

然后,每次某些样式效果只适用于触摸设备,或者只适用于桌面,CSS规则会预先添加相应的类:

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

编辑 :这个策略当然会增加一些额外的字符到你的CSS,所以如果你关心CSS的大小,你可以searchtouchMode和desktopMode的定义,并把它们放到不同的文件,所以你可以为每个设备types; 或者你可以改变类名称之前要prod更短。

我find了两个解决这个问题的方法,这意味着你用modernizr或其他东西来检测触摸,并在html元素上设置一个触摸类。

这是好的,但不是很好的支持

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

但是,这有一个非常好的支持

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

作品完美无瑕,它使所有的hover效果,当你有一个button触摸它会点亮,但不会结束马车作为鼠标事件的初始hover效果。

从非触摸设备检测触摸,我认为modernizr做了最好的工作:

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

编辑

我发现了一个更好更简单的解决scheme

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

对,我有一个类似的问题,但设法解决媒体查询和简单的CSS。 我确信我在这里打破了一些规则,但是这对我有用。

我基本上不得不采取大量的应用程序,并做出反应。 他们使用jQueryUI,并要求我不要篡改他们的任何jQuery,所以我被限制在单独使用CSS。

当我在触摸屏模式下按下其中一个button时,在button的操作生效之前,hover效果将会触发一秒。 这是我如何修复它。

 @media only screen and (max-width:1024px) { #buttonOne{ height: 44px; } #buttonOne:hover{ display:none; } } 

只要触摸触摸屏上的元素,就可以触发mouseLeave事件。 以下是所有<a>标签的解决scheme:

 function removeHover() { var anchors = document.getElementsByTagName('a'); for(i=0; i<anchors.length; i++) { anchors[i].addEventListener('touchstart', function(e){ $('a').mouseleave(); }, false); } } 

这可能有助于看到你的CSS,因为它听起来像一个相当奇怪的问题。 但无论如何,如果它发生,所有其他都很好,你可以尝试将hover效果转移到JavaScript(你也可以使用jQuery)。 简单地说,绑定到鼠标hover或更好的鼠标事件,并在事件发生时点亮你的元素。

在这里查看最后一个例子: http : //api.jquery.com/mouseover/ ,当事件触发时,你可以使用类似的东西来logging下来!

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

然后,只是在决定添加mouseenter和mouseleave侦听器之前检查html标签是否具有非触摸类。

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

对于触摸屏设备,事件将没有听众,所以当您点击时您将不会有hover效果。

在我最近做的一个项目中,我用jQuery的委托事件function解决了这个问题。 它使用jQueryselect器查找某些元素,并在鼠标hover在元素上时向这些元素添加/删除CSS类。 它似乎运作良好,据我所能testing,其中包括运行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'); }); } ); 

编辑:当然,这个解决scheme确实需要你改变你的CSS来删除“:hover”select器,并且预先考虑哪些元素是你想要的。

如果页面上有很多元素(比如几千),它可能会有点慢,因为这个解决scheme捕获页面中所有元素的三种types的事件,如果select器匹配,则会执行它的事情。 我将CSS类命名为“mouseover”,而不是“hover”,因为我不希望任何CSS阅读器读取“:hover”,在其中写入“.hover”。

这是我的解决scheme: http : //jsfiddle.net/agamemnus/g56aw709/–下面的代码。

所有需要做的就是将他们的“:hover”转换为“.hover”…就是这样! 这与其余的最大的区别是,这也将工作在非单一的元素select器,如.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 } }) } } 

在你的页面中包含Modernizr ,并设置你的hover状态,而不是像这样:

 html.no-touchevents .box:hover { background: blue; } 

你好,从未来的人,你可能要使用pointer和/或hover媒体查询。 handheld媒体查询已被弃用。

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

在我的项目中,我们使用https://www.npmjs.com/package/postcss-hover-prefix和https://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的这种后期处理可以禁用触摸设备的hover,并启用其他function。 实际上这个方法是受Bootstrap 4的启发,他们是如何解决同样的问题的: https : //v4-alpha.getbootstrap.com/getting-started/browsers-devices/#sticky-hoverfocus-on-mobile