繁体   English   中英

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

[英]Disable hover effects on mobile browsers

我正在编写一个网站,该网站旨在用于台式机和平板电脑。 当从桌面访问它时,我希望屏幕的可点击区域以: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 的鼠标事件。 您可以使用它来查看点击事件也会触发mouseentermousemovemouseleave

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

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

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

背景

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

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

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

更糟糕的是,如果鼠标悬停事件更改了页面内容,则永远不会触发单击事件,如Safari Web 内容指南 - 处理事件中所述,特别是单指事件中的图 6.4。 “内容更改”究竟是什么,将取决于浏览器和版本。 我发现对于 iOS 7.0,背景颜色的变化不是(或不再是?)内容变化。

解决方案说明

回顾一下:

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

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

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

如果用户实际click sa 链接,则悬停效果也会被移除。 这可确保在用户按下 Web 浏览器中的后退按钮时将其删除。

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

浏览器在touchstart事件和click之间的 300 毫秒延迟实际上得到了很好的利用,因为悬停效果将在这段短时间内显示出来。

如果用户决定取消点击,手指的移动会像往常一样。 通常,这是一个问题,因为没有触发mouseleave事件,并且悬停效果保持不变。 幸运的是,这可以通过移除touchmove上的悬停效果touchmove

就是这样!

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

替代解决方案

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

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

进一步阅读

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

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

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

@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 应用它们,那么您可以专门绑定到鼠标事件,和/或您可以再次仅根据屏幕大小做出一些假设,最坏的“问题”是一些使用鼠标的用户会错过悬停效果。

我为最近的一个项目编写了以下 JS,这是一个桌面/移动/平板电脑站点,具有不应在触摸时出现的悬停效果。

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

每当mouseover事件被触发时, preventMouseover就会被设置回false ,如果用户同时使用他们的触摸屏和鼠标,这允许站点按预期工作。

我们知道mouseover是在touchstart之后触发的,因为它们在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();

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

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

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

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

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

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

我的解决方案是将悬停活动 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;
}

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

我基本上不得不采用某人制作的大型应用程序,并使其具有响应性。 他们使用 jQueryUI 并要求我不要篡改他们的任何 jQuery,所以我只能单独使用 CSS。

当我在触摸屏模式下按下他们的一个按钮时,悬停效果会在按钮的动作生效之前触发一秒钟。 这是我修复它的方法。

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

       #buttonOne{
            height: 44px;
        }


        #buttonOne:hover{
            display:none;
        }
}   

在我的项目中,我们使用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

我为解决相同问题所做的工作是进行特征检测(我使用类似此代码的内容),查看是否定义了 onTouchMove,如果已定义,则将 css 类“touchMode”添加到正文,否则我添加“桌面模式”。

然后,每次某些样式效果仅适用于触摸设备或仅适用于桌面时,css 规则都会添加适当的类:

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

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

我找到了该问题的 2 个解决方案,这意味着您使用 Modernizr 或其他方式检测触摸并在 html 元素上设置触摸类。

这很好,但没有得到很好的支持

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

但这有很好的支持

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

对我来说完美无缺,它使所有悬停效果就像当您触摸按钮时它会亮起但最终不会作为鼠标事件的初始悬停效果出现问题。

从非触摸设备检测触摸我认为 Modernizr 做得最好:

\n

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

编辑

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

如何判断客户端是否为触控设备

每当您触摸触摸屏上的元素时,您都可以触发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);
    }
}

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

然后,在决定添加 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','')
    });
}

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

在我最近做的一个项目中,我用 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”。

这是我的解决方案: 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
   }
  })
 }
}

在你的页面上包含Modernizr并像这样设置你的悬停状态:

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

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

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

 .services-list .fa { transition: 0.5s; -webkit-transform: rotate(0deg); transform: rotate(0deg); color: blue; } /* For me, @media query is the easiest way for disabling hover on mobile devices */ @media only screen and (min-width: 981px) { .services-list .fa:hover { color: #faa152; transition: 0.5s; -webkit-transform: rotate(360deg); transform: rotate(360deg); } } /* You can actiate hover on mobile with :active */ .services-list .fa:active { color: #faa152; transition: 0.5s; -webkit-transform: rotate(360deg); transform: rotate(360deg); } .services-list .fa-car { font-size:20px; margin-right:15px; } .services-list .fa-user { font-size:48px; margin-right:15px; } .services-list .fa-mobile { font-size:60px; }
 <head> <title>Hover effects on mobile browsers</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> </head> <body> <div class="services-list"> <i class="fa fa-car"></i> <i class="fa fa-user"></i> <i class="fa fa-mobile"></i> </div> </body>

例如: https : //jsfiddle.net/lesac4/jg9f4c5r/8/

你可以使用js。 它应该按预期工作。

 function myFunction(){ var x = document.getElementById("DIV"); x.style.backgroundColor="red"; x.style.cursor="pointer"; x.style.color="white" } function my2Function(){ var x = document.getElementById("DIV"); x.style.backgroundColor="white"; x.style.color="red" }
 .mydiv { background-color: white; color: red; }
 <div class = "mydiv" id="DIV" onmouseover="myFunction()" onmouseleave="my2Function()"> hi </div>

在这里,我的问题已通过使用修复(React js 中的鼠标输入和触摸相关问题)

onMouseEnter={() => addHeaderClassName()} onMouseLeave={() => removeHeaderClassName()} onFocus={() => addHeaderClassName()} onBlur={() => removeHeaderClassName()}

上面提到的“onMouseEnter & onMouseLeave”适用于可以检测鼠标事件的台式机等大型设备,另一方面,“onFocus & onBlur”适用于可以检测触摸的平板电脑和移动设备等小型设备。

在这里,我的问题已通过使用修复(React js 中的鼠标输入和触摸相关问题)

onMouseEnter={() => addHeaderClassName()} onMouseLeave={() => removeHeaderClassName()} onFocus={() => addHeaderClassName()} onBlur={() => removeHeaderClassName()}

上面提到的“onMouseEnter & onMouseLeave”适用于可以检测鼠标事件的台式机等大型设备,另一方面,“onFocus & onBlur”适用于可以检测触摸的平板电脑和移动设备等小型设备。

查看您的 CSS 可能会有所帮助,因为这听起来像是一个相当奇怪的问题。 但无论如何,如果它正在发生并且其他一切都很好,您可以尝试将悬停效果转移到 javascript(您也可以使用 jquery)。 简单地,绑定到 mouseover 或更好的 mouseenter 事件并在事件触发时点亮您的元素。

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

试试这个简单的 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 之前,以避免影响工具提示
  • 仅在 iphone XR ios 12.1.12 和 ipad 3 ios 9.3.5 上测试,使用 Safari 或 Chrome。

参考:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM