如何防止触摸设备上的按钮产生粘性悬停效应

我已经创建了一个带有前一个和下一个按钮的旋转木马,它们总是可见的。这些按钮有悬浮状态,它们变成蓝色。在像 iPad 这样的触摸设备上,悬停状态是粘性的,所以点击后按钮保持蓝色。我不想那样。

  • 我可以为每个按钮添加一个 no-hoverontouchend,并使 我的 CSS 是这样的: < code > 按钮: not (. no-hover) : hover { back-color: 蓝色; } 但是这可能对性能非常不利,而且也没有影响 处理像 Chromebook Pixel 这样的设备 触摸屏和鼠标)正确

  • 我可以添加一个 touch类的 documentElement和使我的 CSS 像这样: < code > html: not (. touch)按钮: hover { back-color: blue; 但是在同时使用触摸和编码的设备上也不能正常工作 鼠标

我更喜欢移除悬停状态 ontouchend。但似乎不太可能。聚焦另一个元素并不会移除悬停状态。手动点击另一个元素可以做到这一点,但是在 JavaScript 中似乎无法触发这一点。

我找到的所有解决方案似乎都不完美。有完美的解决方案吗?

140451 次浏览

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

如果通过 onfocus/ontouch设置背景色,一旦 :focus状态消失,颜色样式仍然保留。
您还需要在 onblur上重置,以便在失去焦点时恢复默认 bg。

可以通过临时从 DOM 中删除链接来删除悬停状态


在 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>

这是一个没有完美解决方案的常见问题。悬停行为对鼠标很有用,但对触摸却很有害。使问题更加复杂的是支持触摸和鼠标的设备(同时,不少!)比如 Chromebook 的像素和 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 触摸输入(如手写笔)添加到混合。

使用 现代化,你可以专门为非触摸设备设定悬停目标:

(注意: 这不在 StackOverflow 的代码片段系统上运行,而是检查 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为目标,因为它在移动和桌面上都能正常工作。

由于 CSS 媒体查询级别4的这一部分现在已经是 自2018年以来广泛实施,因此可以使用以下方法:

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

或者用英语说: “如果浏览器支持正确/真实/真实/非模拟悬停(例如,有一个类似鼠标的主要输入设备) ,那么当 button悬停时应用这种风格。”

对于没有实现这个功能的浏览器(或者在原始答案出现时没有实现这个功能) ,我写了一篇填充文章将处理这个问题。使用它,你可以把上面的未来 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);
});

这对我很有帮助: 链接

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

这对我很有用: 把悬停造型放到一个新的类中

.fakehover {background: red}

然后根据需要添加/删除该类

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

对于触发和触发事件重复以上操作。或者任何你想要得到预期结果的事件,例如我想要在触摸屏上切换悬停效果。

$("#elementwithhover").click(function() {
// code that makes element or parent slide or otherwise move out from under mouse.


$(this).clone(true).insertAfter($(this));
$(this).remove();
});

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

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

现在在你的 CSS 中,在每个悬停之前添加这样的悬停类:

.hover .foo:hover {}

如果设备是触摸的,主体类将是空的,否则它的类将悬停并应用规则!

我可以为每个按钮添加一个 no-hover 类,并且使我的 CSS 像 > this: Button: not (. no-hover) : hover { back-color: 但这可能对性能有很大影响,而且不能正确处理像 Chromebook Pixel 这样的设备(它既有触摸屏又有鼠标) > 。

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

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

注意: 如果您希望将其他类应用于 buttonelement,则: not (。Nohover)在 CSS 中将不再像预期的那样工作。然后,您必须添加一个单独的定义与默认值和!重要标签覆盖悬停方式: . nohover {背景色: 白色! 重要}

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

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

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

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

我想摆脱灰色背景出现在 iOSSafari 当链接被点击。但它似乎做得更多。现在单击一个按钮(使用 :hover伪类!)马上就能打开!我只在 iPad 上测试过,不知道是否能在其他设备上使用。

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

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

因此,我只需将 this 处理程序附加到元素的 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>

This, however, only removes :hover pseudoclass from the element after some other touch event has been triggered, like swipe or another touch

基于达伦库克斯的回答,这也工程,如果你移动你的手指在另一个元素。

参见 查找元素手指在接触事件期间处于打开状态

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

代码演示

我本来打算发布我自己的解决方案,但是检查是否有人已经发布了,我发现@Rodney 差点就发布了。然而,他错过了最后一个关键,使它不合适,至少在我的情况下。我的意思是,我也采取了相同的 .fakeHover类添加/删除通过 mouseentermouseleave事件检测,但仅此一点,本质上,几乎完全像“真正的”:hover。我的意思是: 当你点击表中的一个元素时,它不会检测到你已经“离开”了它——因此保持了“虚假悬停”状态。

我所做的仅仅是监听 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();
});

这样,当你使用鼠标在按钮上“停留”时,你就可以保持你通常的 hover功能。嗯,几乎所有的: 唯一的缺点是,不知何故,在用鼠标点击按钮后,它不会处于 hover状态。就像你点击并快速取出按钮的指针。但对我来说,我可以接受。

来自 4种在手机上处理粘性悬停的方法: 这里有一种基于用户当前输入类型向文档动态添加或删除“ 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>

这是我在研究了其余的答案之后得出的结论。它应该能够支持只触摸,只鼠标或混合用户。

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

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

下一个问题是,如果用户想在触摸按钮之后再次使用鼠标,该怎么办?为了解决这个问题,我们需要找到一个合适的时机来添加回我们已经删除的悬停类。

但是,我们不能在删除它之后立即将其添加回去,因为悬停状态仍然处于活动状态。我们也许不想毁掉并重建整个按钮。

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

我知道忙着等待不是很好,但我不确定是否有适当的活动。我已经考虑过将其重新添加到鼠标离开事件中,但是它不是很健壮。例如,当按钮被触摸后弹出一个警报时,鼠标位置发生变化,但是鼠标离开事件没有被触发。

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>

编辑: 我尝试的另一种方法是在触发或触发时调用 e.preventDefault()。它似乎停止悬停效果时,按钮被触摸,但它也停止按钮点击动画,并防止 onclick 函数被调用时,按钮被触摸,所以你必须调用这些手动在 ontouch 启动或 ontouch 处理程序。不是一个很干净的解决方案。

你可以试试这条路。

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

到目前为止,我在我的项目中所做的是恢复触摸设备上的 :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="">。我不是这个“黑客”的粉丝,但它允许 iOS 上的 Safari 对触摸即时做出反应。不知道怎么做到的,但很管用。

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

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

You may want to remove any animation on your touchable class as well. Android Chrome seems to be a little slower than iOS.

This will also result in the active state being applied if the user scrolls the page while touching your class.

我有一个很好的解决方案,我想和大家分享。 首先,你需要检测用户是否像下面这样使用手机:

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

你可以覆盖不支持悬停的设备的悬停效果,比如:

.my-thing {
color: #BADA55;
}


.my-thing:hover {
color: hotpink;
}


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

在 iOS12上进行测试和验证

感谢 https://stackoverflow.com/a/50285058/178959指出这一点。

当浏览器试图操作屏幕时,移动设备上的一些粘滞或卡住的 :hover :focus :active问题可能是由于缺少 <meta name="viewport" content="width=device-width">而造成的。

我也经历过类似的问题,我的应用程序可以兼容所有屏幕大小,在基于 桌面屏幕尺寸/鼠标的设备上有很多悬停效果,后来我意识到基于触摸的设备会导致一种叫做粘滞悬停的状况,这是应用程序在基于触摸的设备用户上正常工作的一个障碍。

我们在应用程序中使用了 SCSS。我定义了一个 混音来处理基于触摸的设备。

@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() {
.animate-icon {
-webkit-transition: all 0.2s;
transition: all 0.2s;
&:hover {
transform: scale(1.3);
filter: brightness(85%);
cursor: pointer;
}
}
}

下面是一些简单的 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现场演示。

我的解决方案是在触摸后克隆和替换节点... 我讨厌这样做,但即使试图重新绘制的偏移高度的元素不工作

    let cloneNode = originNode.cloneNode( true );
originNode.parentNode.replaceChild( cloneNode, originNode );

它可以通过交换一个 HTML 类来实现。它应该比删除整个元素更不容易出现小故障,特别是对于大型的图像链接等。

我们还可以决定是否希望触摸滚动时触发 盘旋状态,甚至添加超时来延迟它们。

我们的代码中唯一重要的改变是在实现新行为的元素上使用额外的 HTML 类,比如 <a class='hover'></a>

超文本标示语言

<a class='my-link hover' href='#'>
Test
</a>

CSS

.my-link:active, // :active can be turned off to disable hover state on 'touchmove'
.my-link.hover:hover {


border: 2px dotted grey;
}

JS (使用 jQuery)

$('.hover').bind('touchstart', function () {


var $el;
$el = $(this);
$el.removeClass('hover');


$el.hover(null, function () {
$el.addClass('hover');
});
});

例子

Https://codepen.io/mattrcouk/pen/vweajzv

-

不过,我没有任何同时具有鼠标和触摸功能的设备来正确测试它。

从2020年开始,可以在媒体查询中添加悬停样式

@media (hover: hover) and (pointer: fine) {
/* css hover class/style */
}

这个媒体查询表明,样式将工作的浏览器,而不是模仿: 悬停,所以它不会工作在触摸浏览器。

这太容易使用 javascrpt 了。那不是悬停问题,那是焦点问题。在使用 css 焦点时将大纲设置为无。

.button:focus {
outline: none;
}

如果你是一个 CSS-in-JS 的家伙,并寻找解决这个问题的办法,这里是。
可以使用 JS 媒体查询在 CSS 中实现媒体查询。

例如,下面的代码片段只在屏幕大小大于768px 的情况下向按钮添加悬停效果。

tag: {
cursor: "pointer",
"&:hover, &:active": window.matchMedia('(min-width: 768px)').matches ? {
transform: "scale(1.3)"
} : null
}

Kevin Lee 的回答 包含了一些我在其他地方没有见过的结尾部分,它帮助我用很少的工作就解决了这个 多输入系统(像一个触摸屏笔记本电脑)。

function f() {
console.log("Hi");
}
button:hover {
border: 2px solid blue;
}
<button type="button" onclick="f()" ontouchend="f(); event.preventDefault()">
Say Hi
</button>

在一个多输入设备上运行这个命令,你会看到鼠标指针悬停会产生一个蓝色边框,但是按下触摸屏上的按钮就不会产生蓝色边框。

唯一的缺点是,在你的 UI 框架(涟漪动画等)的任何其他点击功能可能不会触发。你也许可以自己手动发射——对我来说,这从一开始就不是问题。