首页 文章

如何防止触摸设备上按钮的粘滞悬停效果

提问于
浏览
118

我创建了一个带有前一个和下一个按钮的旋转木马,它们始终可见 . 这些按钮具有悬停状态,它们变为蓝色 . 在触控设备(如iPad)上,悬停状态是粘性的,因此按下按钮后按钮会保持蓝色 . 我不希望这样 .

  • 我可以为每个按钮添加 no-hoverontouchend ,并使我的CSS像这样: button:not(.no-hover):hover { background-color: blue; } 但是's probably quite bad for performance, and doesn't正确处理Chromebook Pixel(同时具有触摸屏和鼠标)等设备 .

  • 我可以将 touch 类添加到 documentElement 并使我的CSS像这样: html:not(.touch) button:hover { background-color: blue; } 但是这也无法在具有触摸和鼠标的设备上正常工作 .

我更喜欢的是删除悬停状态 ontouchend . 但它没有删除悬停状态 . 手动点击另一个元素,但我似乎无法在JavaScript中触发它 .

我发现的所有解决方案似乎都不完美 . 有完美的解决方案吗?

21 回答

  • 50

    您可以通过暂时从DOM中删除链接来删除悬停状态 . 见http://testbug.handcraft.com/ipad.html


    在CSS中你有:

    :hover {background:red;}
    

    在JS中你有:

    function fix()
    {
        var el = this;
        var par = el.parentNode;
        var next = el.nextSibling;
        par.removeChild(el);
        setTimeout(function() {par.insertBefore(el, next);}, 0)
    }
    

    然后在你的HTML中你有:

    <a href="#" ontouchend="this.onclick=fix">test</a>
    
  • 0

    实施CSS Media Queries Level 4后,您将能够执行此操作:

    @media (hover: hover) {
        button:hover {
            background-color: blue;
        }
    }
    

    或者用英语:“如果浏览器支持正确/真实/真实/非模拟悬停(例如,具有类似鼠标的主要输入设备),则在 button 悬停时应用此样式 . ”

    由于Media Queries Level 4的这一部分到目前为止只在最前沿的Chrome中实现,I wrote a polyfill才能解决这个问题 . 使用它,您可以将上面的未来派CSS转换为:

    html.my-true-hover button:hover {
        background-color: blue;
    }
    

    .no-touch 技术的变体)然后使用来自检测支持悬停的相同polyfill的一些客户端JavaScript,您可以相应地切换 my-true-hover 类的存在:

    $(document).on('mq4hsChange', function (e) {
        $(document.documentElement).toggleClass('my-true-hover', e.trueHover);
    });
    
  • 0

    这是一个常见的问题,没有完美的解决方案 . 悬停行为对鼠标很有用,而且对触摸大多不利 . 使问题更加复杂的是支持触摸和鼠标(同时,不低于!)的设备,如Chromebook Pixel和Surface .

    我发现最干净的解决方案是仅在设备不被视为支持触摸输入时才启用悬停行为 .

    var isTouch =  !!("ontouchstart" in window) || window.navigator.msMaxTouchPoints > 0;
    
    if( !isTouch ){
        // add class which defines hover behavior
    }
    

    当然,你会丢失悬停在可能支持它的设备上 . 但是,有时候悬停的影响比链接本身更多,例如也许你想在元素悬停时显示一个菜单 . 这种方法允许您测试触摸的存在,并可能有条件地附加不同的事件 .

    我已经在iPhone,iPad,Chromebook Pixel,Surface和各种Android设备上进行了测试 . 当将通用USB触摸输入(例如手写笔)添加到混音中时,我无法保证它能够正常工作 .

  • 5

    使用Modernizr,您可以专门针对无触摸设备定位您的悬停:

    (注意:这不是't run on StackOverflow' s片段系统,请检查jsfiddle

    /* this one is sticky */
    #regular:hover, #regular:active {
      opacity: 0.5;
    }
    
    /* this one isn't */
    html.no-touch #no-touch:hover, #no-touch:active {
      opacity: 0.5;
    }
    

    请注意 :active 不需要以 .no-touch 为目标,因为它在移动设备和桌面设备上都能正常工作 .

  • 1

    4 ways to deal with sticky hover on mobile:这是一种根据用户的当前输入类型动态添加或删除文档的“ can touch ”类的方法 . 它适用于混合设备,用户可以在触摸和鼠标/触控板之间切换:

    <script>
    
    ;(function(){
        var isTouch = false //var to indicate current input type (is touch versus no touch) 
        var isTouchTimer 
        var curRootClass = '' //var indicating current document root class ("can-touch" or "")
    
        function addtouchclass(e){
            clearTimeout(isTouchTimer)
            isTouch = true
            if (curRootClass != 'can-touch'){ //add "can-touch' class if it's not already present
                curRootClass = 'can-touch'
                document.documentElement.classList.add(curRootClass)
            }
            isTouchTimer = setTimeout(function(){isTouch = false}, 500) //maintain "istouch" state for 500ms so removetouchclass doesn't get fired immediately following a touch event
        }
    
        function removetouchclass(e){
            if (!isTouch && curRootClass == 'can-touch'){ //remove 'can-touch' class if not triggered by a touch event and class is present
                isTouch = false
                curRootClass = ''
                document.documentElement.classList.remove('can-touch')
            }
        }
    
        document.addEventListener('touchstart', addtouchclass, false) //this event only gets called when input type is touch
        document.addEventListener('mouseover', removetouchclass, false) //this event gets called when input type is everything from touch to mouse/ trackpad
    })();
    
    </script>
    
  • 0
    $("#elementwithhover").click(function() { 
      // code that makes element or parent slide or otherwise move out from under mouse. 
    
      $(this).clone(true).insertAfter($(this));
      $(this).remove();
    });
    
  • 0

    我打算发布我自己的解决方案,但检查是否有人发布了它,我发现@Rodney几乎做到了 . 然而,至少在我的情况下,他错过了最后一个让它变得不明智的关键 . 我的意思是,我也通过 mouseentermouseleave 事件检测采用相同的 .fakeHover 类添加/删除,但仅此一项,其行为几乎与"genuine" :hover 完全相同 . 我的意思是:当你点击表格中的一个元素时,它不会检测到你有"leaved"它 - 从而保持"fakehover"状态 .

    我所做的只是在 click 上听,所以当我按下按钮时,我会手动触发 mouseleave .

    这是我的最终代码:

    .fakeHover {
        background-color: blue;
    }
    
    $(document).on('mouseenter', 'button.myButton',function(){
        $(this).addClass('fakeHover');
    });
    
    $(document).on('mouseleave', 'button.myButton',function(){
        $(this).removeClass('fakeHover');
    });
    
    $(document).on('button.myButton, 'click', function(){
        $(this).mouseleave();
    });
    

    这样,当您在按钮上使用鼠标时,只需"hovering"就可以保留常用的 hover 功能 . 好吧,几乎全部:唯一的缺点是,用鼠标点击按钮后,它不会处于 hover 状态 . 就像你点击并快速将指针从按钮中取出一样 . 但就我而言,我可以忍受 .

  • 0

    它是对我有帮助:link

    function hoverTouchUnstick() {
        // Check if the device supports touch events
        if('ontouchstart' in document.documentElement) {
            // Loop through each stylesheet
            for(var sheetI = document.styleSheets.length - 1; sheetI >= 0; sheetI--) {
                var sheet = document.styleSheets[sheetI];
                // Verify if cssRules exists in sheet
                if(sheet.cssRules) {
                    // Loop through each rule in sheet
                    for(var ruleI = sheet.cssRules.length - 1; ruleI >= 0; ruleI--) {
                        var rule = sheet.cssRules[ruleI];
                        // Verify rule has selector text
                        if(rule.selectorText) {
                            // Replace hover psuedo-class with active psuedo-class
                            rule.selectorText = rule.selectorText.replace(":hover", ":active");
                        }
                    }
                }
            }
        }
    }
    
  • 1

    将此JS代码添加到您的页面:

    document.body.className = 'ontouchstart' in document.documentElement ? '' : 'hover';
    

    现在在你的CSS之前每次悬停添加像这样的悬停类:

    .hover .foo:hover {}
    

    如果设备是触摸的,则body类将为空,否则其类将悬停并应用规则!

  • 1

    我可以为每个按钮添加一个无悬停类ontouchend,并使我的CSS像> this:button:not(.no-hover):hover {background-color:blue;但这可能对性能非常不利,并且无法正确处理Chromebook Pixel(同时具有触摸屏和鼠标)等设备 .

    这是正确的起点 . 下一步:对以下事件应用/删除nohover类(使用jQuery演示)

    buttonelement
     .on("touchend touchcancel",function(){$(this).addClass("nohover")})
     .on("touchstart mouseover",function({$(this).removeClass("nohover")});
    

    注意:如果您希望将其他类应用于buttonelement,则CSS中的not(.nohover)将不再按预期工作 . 比你必须添加一个单独的定义与默认值和!important标签来覆盖悬停样式:.nohover {background-color:white!important}

    这甚至可以正确处理Chromebook Pixel(同时具有触摸屏和鼠标)等设备!我不认为,这是一个主要的性能杀手......

  • 49

    一个对我有用的解决方案:

    html {
       -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
    }
    

    将此代码添加到样式表中 .

    我想摆脱点击链接时iOS Safari上出现的灰色背景 . 但它似乎做得更多 . 现在点击一个按钮(带有 :hover 伪类!)立即打开!我只是在iPad上测试过,我不会在其他设备上工作 .

  • 1

    我想我找到了一个类似问题的优雅(最小js)解决方案:

    使用jQuery,你可以使用 .mouseover() 触发悬浮在body(或任何其他元素)上

    所以我只是将这个处理程序附加到元素的 ontouchend 事件中,如下所示:

    var unhover = function() {
      $("body").mousover();  
    };
    
    .hoverable {
      width: 100px;
      height: 100px;
      background: teal;
      cursor: pointer;
    }
    
    .hoverable:hover {
      background: pink;
    }
    
    <div class="hoverable" ontouchend={unhover}></div>
    

    但是,这只会消除:在触发某些其他触摸事件后,从元素中悬停伪类,如滑动或其他触摸

  • 6

    这是我在研究其余答案后到目前为止所提出的 . 它应该能够支持仅触摸,仅鼠标或混合用户 .

    为悬停效果创建单独的悬停类 . 默认情况下,将此悬停类添加到我们的按钮 .

    我们不希望检测触摸支持的存在并从一开始就禁用所有悬停效果 . 正如其他人所提到的,混合设备越来越受欢迎;人们可能有触摸支持,但想要使用鼠标,反之亦然 . 因此,仅在用户实际触摸按钮时删除悬停类 .

    接下来的问题是,如果用户想要在触摸按钮后返回使用鼠标怎么办?要解决这个问题,我们需要找到一个适当的时机来添加我们已删除的悬停类 .

    但是,我们无法在删除它后立即将其添加回来,因为悬停状态仍处于活动状态 . 我们可能也不想破坏和重新创建整个按钮 .

    所以,我想过使用忙等待算法(使用setInterval)来检查悬停状态 . 一旦停用悬停状态,我们就可以添加回悬停类并停止忙等待,使我们回到用户可以使用鼠标或触摸的原始状态 .

    我知道忙碌的等待不是很好,但我不确定是否有适当的事件 . 我考虑过将它添加回mouseleave事件,但它不是很强大 . 例如,当触摸按钮后弹出警报时,鼠标位置会移动但不会触发mouseleave事件 .

    var button = document.getElementById('myButton');
    
    button.ontouchstart = function(e) {
      console.log('ontouchstart');
      $('.button').removeClass('button-hover');
      startIntervalToResetHover();
    };
    
    button.onclick = function(e) {
      console.log('onclick');
    }
    
    var intervalId;
    
    function startIntervalToResetHover() {
      // Clear the previous one, if any.
      if (intervalId) {
        clearInterval(intervalId);
      }
      
      intervalId = setInterval(function() {
        // Stop if the hover class already exists.
        if ($('.button').hasClass('button-hover')) {
          clearInterval(intervalId);
          intervalId = null;
          return;
        }
        
        // Checking of hover state from 
        // http://stackoverflow.com/a/8981521/2669960.
        var isHovered = !!$('.button').filter(function() {
          return $(this).is(":hover");
        }).length;
        
        if (isHovered) {
          console.log('Hover state is active');
        } else {
          console.log('Hover state is inactive');
          $('.button').addClass('button-hover');
          console.log('Added back the button-hover class');
          
          clearInterval(intervalId);
          intervalId = null;
        }
      }, 1000);
    }
    
    .button {
      color: green;
      border: none;
    }
    
    .button-hover:hover {
      background: yellow;
      border: none;
    }
    
    .button:active {
      border: none;
    }
    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <button id='myButton' class='button button-hover'>Hello</button>
    

    编辑:我尝试的另一种方法是在ontouchstart或ontouchend中调用 e.preventDefault() . 触摸按钮时似乎会停止悬停效果,但它也会停止按钮单击动画并阻止触摸按钮时调用onclick功能,因此您必须在ontouchstart或ontouchend处理程序中手动调用它们 . 不是很干净的解决方案 .

  • 25

    您可以覆盖不支持悬停的设备的悬停效果 . 喜欢:

    .my-thing {
        color: #BADA55;
    }
    
    .my-thing:hover {
        color: hotpink;
    }
    
    @media (hover: none) {
        .my-thing {
            color: #BADA55;
        }
    }
    

    在iOS 12上经过测试和验证

    帽子提示https://stackoverflow.com/a/50285058/178959指出这个 .

  • 1

    您可以在 :active 状态下设置背景颜色,并为 :focus 提供defaut背景 .

    如果你通过 onfocus/ontouch 设置背景颜色,颜色样式一旦 :focus 状态消失了 .
    您需要重置 onblur 以便在焦点丢失时恢复defaut bg .

  • 1

    这对我有用:将悬停样式放在一个新类中

    .fakehover {background: red}
    

    然后在需要时添加/删除类

    $(".someclass > li").on("mouseenter", function(e) {
      $(this).addClass("fakehover");
    });
    $(".someclass > li").on("mouseleave", function(e) {
      $(this).removeClass("fakehover");
    });
    

    重复touchstart和touchend事件 . 或者您喜欢的任何事件以获得所需的结果,例如我希望在触摸屏上切换悬停效果 .

  • 2

    根据Darren Cooks的答案,如果你将手指放在另一个元素上,这个答案也会有效 .

    Find element finger is on during a touchend event

    jQuery(function() {
        FastClick.attach(document.body);
    });
    // Prevent sticky hover effects for buttons on touch devices
    // From https://stackoverflow.com/a/17234319
    //
    //
    // Usage:
    // <a href="..." touch-focus-fix>..</a>
    //
    // Refactored from a directive for better performance and compability
    jQuery(document.documentElement).on('touchend', function(event) {
      'use strict';
    
      function fix(sourceElement) {
        var el = $(sourceElement).closest('[touch-focus-fix]')[0];
        if (!el) {
          return;
        }
        var par = el.parentNode;
        var next = el.nextSibling;
        par.removeChild(el);
        par.insertBefore(el, next);
      }
    
      fix(event.target);
      var changedTouch = event.originalEvent.changedTouches[0];
      // http://www.w3.org/TR/2011/WD-touch-events-20110505/#the-touchend-event
      if (!changedTouch) {
        return;
      }
      var touchTarget = document.elementFromPoint(changedTouch.clientX, changedTouch.clientY);
      if (touchTarget && touchTarget !== event.target) {
        fix(touchTarget);
      }
    });
    

    Codepen演示

  • 10

    你可以试试这种方式 .

    JavaScript的:

    var isEventSupported = function (eventName, elementName) {
        var el = elementName ? document.createElement(elementName) : window;
        eventName = 'on' + eventName;
        var isSupported = (eventName in el);
        if (!isSupported && el.setAttribute) {
            el.setAttribute(eventName, 'return;');
            isSupported = typeof el[eventName] == 'function';
        }
        el = null;
        return isSupported;
    };
    
    if (!isEventSupported('touchstart')) {
        $('a').addClass('with-hover');
    }
    

    CSS:

    a.with-hover:hover {
      color: #fafafa;
    }
    
  • 0

    到目前为止我在我的项目中所做的是恢复触摸设备上的更改 :hover

    .myhoveredclass {
        background-color:green;
    }
    .myhoveredclass:hover {
        background-color:red;
    }
    @media screen and (-webkit-min-device-pixel-ratio:0) {
        .myhoveredclass:hover, .myhoveredclass:active, .myhoveredclass:focus {
            background-color:green;
        }
    }
    

    所有类名和命名颜色仅用于演示目的;-)

  • 1

    这完美地分为两步 .

    • 将您的身体标记设置为 <body ontouchstart=""> . 我不是这个"hack"的粉丝,但它允许iOS上的Safari立即对触摸作出反应 . 不确定如何,但它的工作原理 .

    • 像这样设置你的可触摸类:

    // I did this in SASS, but this should work with normal CSS as well
    
    // Touchable class
    .example {
    
        // Default styles
        background: green;
    
        // Default hover styles 
        // (Think of this as Desktop and larger)
        &:hover {
            background: yellow;
        }
    
        // Default active styles
        &:active {
            background: red;
        }
    
        // Setup breakpoint for smaller device widths
        @media only screen and (max-width: 1048px) {
    
            // Important!
            // Reset touchable hover styles
            // You may want to use the same exact styles as the Default styles
            &:hover {
                background: green;
            }
    
            // Important!
            // Touchable active styles
            &:active {
                background: red;
            }
        }
    }
    

    您可能还想删除可触摸类上的任何动画 . Android Chrome似乎比iOS慢一点 .

    如果用户在触摸您的课程时滚动页面,这也将导致应用活动状态 .

  • 0

    我有一个很好的解决方案,我想分享 . 首先,您需要检测用户是否在移动设备上,如下所示:

    var touchDevice = /ipad|iphone|android|windows phone|blackberry/i.test(navigator.userAgent.toLowerCase());
    

    然后添加:

    if (!touchDevice) {
        $(".navbar-ul").addClass("hoverable");
    }
    

    在CSS中:

    .navbar-ul.hoverable li a:hover {
        color: #fff;
    }
    

相关问题