首页 文章

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

提问于
浏览
110

我'm writing a Web site that' s意味着可以从台式机和平板电脑使用 . 当从桌面访问它时,我希望屏幕的可点击区域以 :hover 效果(不同的背景颜色等)点亮 . 对于平板电脑,'s no mouse, so I don't需要任何悬停效果 .

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

How can I get the hover effects when I'm using the mouse, but suppress them when I'm using the touchscreen?

如果有人想提出建议,我不想使用用户代理嗅探 . 同样的设备可能同时具有触摸屏和鼠标(今天可能不常见,但将来会更加如此) . 我对该设备不感兴趣,我对它目前的使用方式感兴趣:鼠标或触摸屏 .

我已经尝试挂钩 touchstarttouchmovetouchend 事件并在所有事件上调用 preventDefault() ,这确实在某些时候抑制了"invisible mouse cursor";但是如果我在两个不同的元素之间来回快速点击,经过几次点击之后它就会开始移动"mouse cursor"然后点亮悬停效果 - 就像我的 preventDefault isn 't always honored. I won' t,除非必要,否则我会为你提供细节 - I 'm not even sure that'采取正确的方法;如果有人有更简单的解决方案,我会全力以赴 .


Edit: 这可以用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>

如果你鼠标悬停在其中任何一个框上,它将获得我想要的蓝色背景 . 但是如果你点击其中任何一个盒子,它也会得到一个蓝色背景,这是我试图阻止的事情 .

我还发布了一个样本here,它可以完成上述操作并挂钩jQuery的鼠标事件 . 您可以使用它来查看点击事件也将触发 mouseentermousemovemouseleave .

16 回答

  • 2

    我从你的问题中得知你的悬停效果会改变你网页的内容 . 在这种情况下,我的建议是:

    • Add hover effects on touchstart and mouseenter.

    • Remove hover effects on mouseleave, touchmove and click.

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

    背景

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

    • touchstart

    • touchmove

    • touchend

    • 300ms延迟,浏览器确保这是一次点击,而不是双击

    • mouseover

    • mouseenter

    • Note :如果 mouseovermouseentermousemove 事件更改了页面内容,则永远不会触发以下事件 .

    • mousemove

    • mousedown

    • mouseup

    • click

    It does not seem possible to simply tell the webbrowser to skip the mouse events.

    更糟糕的是,如果鼠标悬停事件改变了页面内容,则不会触发click事件,如Safari Web Content Guide - Handling Events所述,特别是单手事件中的图6.4 . 究竟"content change"究竟是什么,取决于浏览器和版本 . 我发现对于iOS 7.0,背景颜色的变化不是(或不再是?)内容更改 .

    解决方案解释

    回顾一下:

    • touchstartmouseenter 上添加悬停效果 .

    • 删除 mouseleavetouchmoveclick 上的悬停效果 .

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

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

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

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

    浏览器在 touchstart 事件和 click 之间的300ms延迟实际上得到了充分利用,因为悬停效果将在此短时间内显示 .

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

    而已!

    请注意,这是可能的删除300ms延迟,例如使用FastClick library,但这超出了这个问题的范围 .

    替代解决方案

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

    • browser detection: 极易出错 . 假设设备具有鼠标或触摸功能,而当触摸显示器增加时,两者的组合将变得越来越常见 .

    • CSS media detection: 我唯一知道的CSS解决方案 . 仍然容易出错,并且仍然认为设备有鼠标或触摸,而两者都是可能的 .

    • Emulate the click event in touchend: 这将错误地跟随链接,即使用户只想滚动或缩放,而无意实际点击链接 .

    • Use a variable to suppress mouse events: 这在 touchend 中设置了一个变量,该变量在后续鼠标事件中用作if条件,以防止在该时间点发生状态变化 . 该变量在click事件中重置 . 请参阅Walter Roman 's answer on this page. This is a decent solution if you really don' t想要在触摸界面上悬停效果 . 不幸的是,如果 touchend 由于其他原因被触发并且没有触发任何点击事件(例如用户滚动或缩放),并且随后尝试使用鼠标跟踪链接(即在具有鼠标和触摸的设备上),则这不起作用接口) .

    进一步阅读

  • 1

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

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

    如果要在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应用它们,那么你可以专门绑定到鼠标事件,和/或你可能只是根据屏幕大小做出一些假设,最糟糕的“问题”是一些正在使用鼠标的用户错过了悬停效果 .

  • 80

    我为最近的一个项目编写了以下JS,这是一个桌面/移动/平板电脑网站,其悬浮效果不应该出现在触摸上 .

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

    只要 mouseover 事件被触发, preventMouseover 就会被设置回 false ,这样如果用户同时使用他们的触摸屏和鼠标,网站就可以按预期工作 .

    我们知道 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();
    
  • 0

    我的解决方案是将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;
    }
    
  • 1

    我自己真的想要一个纯粹的 css 解决方案,因为在我的所有视图周围散布一个重量级的JavaScript解决方案似乎是一个令人不快的选择 . 最后找到@media.hover查询,它可以检测"whether the primary input mechanism allows the user to hover over elements."这避免了触摸设备,其中"hovering"更多是模拟动作而不是输入设备的直接功能 .

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

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

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

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

    虽然大多数现代浏览器都支持交互媒体功能查询,但一些流行的浏览器却不支持.1709960_ . 在我的情况下,这很好用,因为我只打算在桌面上支持Chrome,在移动设备上支持Chrome和Safari .

  • 5

    我为解决同样的问题所做的就是进行特征检测(我使用像this code这样的东西),查看是否定义了onTouchMove,如果是,我将css类"touchMode"添加到正文中,否则我添加"desktopMode" .

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

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

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

  • 0

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

  • 9

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

    我基本上不得不接受某人制作的大量应用程序,并使其响应 . 他们使用jQueryUI并要求我不要篡改他们的任何jQuery,所以我被限制只使用CSS .

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

    @media only screen and (max-width:1024px) {
    
           #buttonOne{
                height: 44px;
            }
    
    
            #buttonOne:hover{
                display:none;
            }
    }
    
  • 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);
        }
    }
    
  • 2

    我发现了问题的2个解决方案,它暗示你用modernizr或其他东西检测触摸,并在html元素上设置触摸类 .

    这很好,但不是很好: supported

    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

    EDIT

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

    How to determine if the client is a touch device

  • 0

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

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

  • 19

    如果您乐意使用JavaScript,则可以在页面中使用Modernizr . 加载页面时,非触摸屏浏览器会将类'.no-touch'添加到html标记中,但对于触摸屏浏览器,html标记会将类'.touch'添加到html标记中 .

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

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

  • 5

    在我最近做的一个项目中,我用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'); });
        }
    );
    

    edit: 这个解决方案当然要求你改变你的CSS以删除":hover"选择器,并且提前考虑你想成为哪些元素"hoverable" .

    如果你的页面上有很多元素(比如几千个),它可能会有点慢,因为这个解决方案会捕获页面中所有元素的三种类型的事件,然后如果选择器匹配则执行它的操作 . 我将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

    在您的页面上包含Modernizr并将此类悬停状态设置为:

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

    你好,将来,你可能想要使用 pointer 和/或 hover 媒体查询 . 不推荐使用 handheld 媒体查询 .

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

相关问题