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

213

我创建了一个旋转木马,并添加了上一个和下一个按钮,这些按钮始终可见。这些按钮有悬停状态,它们会变成蓝色。在iPad等触摸设备上,悬停状态是粘性的,因此在点击后按钮会保持蓝色。但我不希望出现这种情况。

  • 我可以为每个按钮添加一个no-hover类,并在ontouchend时更改CSS如下:button:not(.no-hover):hover { background-color: blue; } 但这可能对性能影响较大,并且不能正确处理像Chromebook Pixel(具有触摸屏和鼠标)这样的设备。

  • 我可以向documentElement添加touch类,并将我的CSS更改如下:html:not(.touch) button:hover { background-color: blue;} 但是这也不能在同时具有触摸和鼠标的设备上正确工作。

我更喜欢在ontouchend时删除悬停状态。但似乎这是不可能的。将焦点放在另一个元素上并不能删除悬停状态。手动点击其他元素可以,但我似乎无法在JavaScript中触发该事件。

我找到的所有解决方案都不完美。是否有完美的解决方案?


6
https://css-tricks.com/solving-sticky-hover-states-with-media-hover-hover/ - dasfdsa
这是一个相当不错的解决方案,@dasfdsa!然而,它并不适用于同时支持触摸屏和鼠标的设备。 - Chris
如果使用Tailwind CSS,它会像这样:[@media(hover:hover)]:hover:bg-red-400。只有在前面的任意变量为真时,它才会应用该悬停样式。 - zenw0lf
30个回答

2
我曾经遇到过类似的问题,我的应用程序在所有屏幕尺寸上都兼容,并且在桌面屏幕尺寸/基于鼠标的设备上有很多悬停效果,后来我意识到基于触摸的设备会导致一种称为“粘滞悬停”的情况,这对于基于触摸的设备用户来说是一个障碍。
我们在应用程序中使用了SCSS,并定义了一个mixin来处理基于触摸的设备。
@mixin hover-support {
  @media not all and (pointer: coarse) {
    &:hover {
      @content;
    }
  }
}

然后我将所有的CSS类放在下面提到的代码片段下面。
   @include hover-support() {
    // Your css-classes or css that you want to apply on those devices that support hover.
   }

例如,我们有一个用于动画图标的类,当我们悬停在图标上时会触发,如您从CSS中可以看到的那样。但在基于触摸的设备上,它受到粘性悬停效果的影响,因此我将其放置在@include hover-support()内,以确保悬停仅适用于支持悬停的设备。
@include hover-support() {
  .animate-icon {
    -webkit-transition: all 0.2s;
    transition: all 0.2s;
    &:hover {
      transform: scale(1.3);
      filter: brightness(85%);
      cursor: pointer;
    }
  }
}

2
这是一些简单的JavaScript代码,不需要开发人员编辑CSS或编写任何新的CSS规则。我为具有class ="btn"的Bootstrap按钮编写了这个代码,但它也适用于具有特定类名的任何按钮。
步骤如下:
1.确定这是否是触摸设备
2.如果是,则迭代document.styleSheets中的每个CSS规则
3.删除包含.btn:hover的任何规则
消除所有.btn:hover CSS规则确保按钮上没有视觉悬停效果。
第1步:检测触摸设备
检查媒体查询是否存在(hover: none)
    const hasMatchMedia = typeof window.matchMedia !== 'undefined';
    /**
     * determine if device is touch-capable
     * true - device is touch-capable
     * false - device is not touch-capable
     * null - unable to determine touch capability
     * @return {null|boolean}
     */
    const hasTouch = () => {
      if (hasMatchMedia) {
        return window.matchMedia('(hover: none)').matches;
      }
      return null;
    };

第二步:删除包含`btn`和`:hover`的CSS规则。
    /**
     * remove all CSS rules contaning both '.btn' and ':hover'
     * @return {number} count of rules deleted
     */
    function removeBtnHovers () {

      let rulesDeleted = 0;

      // recursively delete '.btn:hover' rules
      function recursiveDelete (rules, styleSheet) {

        if (typeof rules === 'undefined' ||
          typeof rules.length === 'undefined' ||
          rules.length <= 0) {
          return;
        }

        // iterate in reverse order,
        // deleting any rule containing both '.btn' and ':hover'
        const ruleLen = rules.length;
        for (let i = ruleLen - 1; i >= 0; i--) {
          const rule = rules[i];
          if (typeof rule.cssRules === 'undefined') {
            // a standard rule, evaluate it
            const cssText = rule.cssText;
            if (typeof cssText === 'string' &&
              cssText.includes('.btn') &&
              cssText.includes(':hover')) {
              styleSheet.deleteRule(i);
              rulesDeleted++;
            }
          } else {
            // rule contains cssRules, iterate over them
            recursiveDelete(rule.cssRules, rule);
          }
        }
      }

      // iterate over all style sheets in document
      for (const styleSheet of document.styleSheets) {
        let rules = styleSheet.cssRules;
        if (!rules) { continue; }
        recursiveDelete(rules, styleSheet);
      }
      return rulesDeleted;
    }

完整的代码在GitHubnpm上。

terrymorse.com上有实时演示。


这是否也会删除对于使用鼠标/触摸板的触摸屏设备的悬停效果? - Jensei
@Jensei 可能。如果设备匹配 @media (hover:none),或者用户首次触摸屏幕,则悬停规则将被移除。您可以在实时演示页面上尝试它,以确保它正常工作。 - terrymorse

1
我可以为每个按钮添加一个ontouchend的no-hover类,并将CSS设置如下: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")});   

注意:如果您希望将其他类应用于按钮元素,则CSS中的:not(.nohover)将不再按预期工作。然后,您需要添加一个单独的定义,具有默认值和!important标记以覆盖悬停样式:.nohover{ background-color: white !important}
这甚至可以正确处理像Chromebook Pixel这样同时拥有触摸屏和鼠标的设备!我认为这不会对性能造成重大影响...

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

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

将此代码添加到你的样式表中。
我想要消除在iOS Safari中点击链接时出现的灰色背景。但它似乎做了更多的事情。现在,立即点击一个带有:hover伪类的按钮就会打开!我只在iPad上测试过,不知道它是否适用于其他设备。

1
将以下JS代码添加到您的页面中:

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

现在在你的CSS中,在每个hover之前添加hover类,如下所示:

.hover .foo:hover {}

如果设备是触摸屏,body类将为空,否则它的类将为hover并应用规则!

不适用于我,但这个可以:document.body.className = 'ontouchstart' in window ? '' : 'hover'; - drskullster

1

这对我很有帮助:link

(保留HTML格式)
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");
                    }
                }
            }
        }
    }
}

您需要从全局ontouchstart事件处理程序中调用hoverTouchUnstick()一次。这种解决方案将完美地解决问题。 - Jusid

1

我有一个好的解决方案想要分享。首先,您需要像这样检测用户是否正在使用移动设备:

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

2
不支持混合设备,例如 Windows 触摸屏电脑。 - cspolton

0

这在两个步骤中完美运行。

  1. 将您的body标签设置为这样<body ontouchstart="">。我不是这个“hack”的粉丝,但它允许iOS上的Safari立即响应触摸。不知道为什么,但它确实有效。

  2. 像这样设置可触摸的类:

    // 我用SASS做了这个,但这也适用于普通的CSS
    
    // 可触摸的类
    .example {
    
        // 默认样式
        background: green;
    
        // 默认悬停样式
        // (把它看作桌面和更大的设备)
        &:hover {
            background: yellow;
        }
    
        // 默认活动样式
        &:active {
            background: red;
        }
    
        // 设置较小设备宽度的断点
        @media only screen and (max-width: 1048px) {
    
            // 重要!
            // 重置可触摸的悬停样式
            // 您可能希望使用与默认样式完全相同的样式
            &:hover {
                background: green;
            }
    
            // 重要!
            // 可触摸的活动样式
            &:active {
                background: red;
            }
        }
    }
    

你可能也想要移除你的可触摸类上的任何动画效果。Android Chrome 似乎比 iOS 稍微慢一些。

这也会导致如果用户在触摸你的类时滚动页面,活动状态也会被应用。


0
你可以尝试这种方式。
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

基于Darren Cook的答案,如果您将手指移动到另一个元素上,它也可以工作。

请参见在touchend事件期间查找手指所在的元素

jQuery(function() {
    FastClick.attach(document.body);
});
// Prevent sticky hover effects for buttons on touch devices
// From https://dev59.com/gmQm5IYBdhLWcg3w6ShR#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演示


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接