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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8291517/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 05:26:38  来源:igfitidea点击:

Disable hover effects on mobile browsers

javascripthovertouch

提问by Joe White

I'm writing a Web site that's meant to be used from both desktops and tablets. When it's being visited from a desktop, I want the clickable areas of the screen to light up with :hovereffects (different background color, etc.) With a tablet, there's no mouse, so I don't want any hover effects.

我正在编写一个网站,该网站旨在用于台式机和平板电脑。当从桌面访问它时,我希望屏幕的可点击区域点亮:hover效果(不同的背景颜色等)。平板电脑没有鼠标,所以我不想要任何悬停效果。

The problem is, when I tap something on the tablet, the browser evidently has some kind of "invisible mouse cursor" that it moves to the location I tapped, and then leaves it there -- so the thing I just tapped lights up with a hover effect until I tap something else.

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

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

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

In case someone was thinking of suggesting it, I don't want to use user-agent sniffing. The same device could have both a touchscreen and a mouse (maybe not so common today, but much more so in the future). I'm not interested in the device, I'm interested in how it's currently being used: mouse or touchscreen.

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

I already tried hooking the touchstart, touchmove, and touchendevents and calling preventDefault()on all of them, which does suppress the "invisible mouse cursor" some of the time; but if I tap rapidly back and forth between two different elements, after a few taps it will start moving the "mouse cursor" and lighting up the hover effects anyway -- it's like my preventDefaultisn't always honored. I won't bore you with the details unless necessary -- I'm not even sure that's the right approach to take; if anyone has a simpler fix, I'm all ears.

我已经尝试挂钩touchstarttouchmovetouchend事件,并呼吁preventDefault()对所有的人,这确实抑制“隐形鼠标”的一些时间; 但是如果我在两个不同的元素之间快速来回轻敲,轻敲几下后,它会开始移动“鼠标光标”并无论如何都会点亮悬停效果——就像我preventDefault并不总是受到尊重。除非必要,否则我不会让你厌烦细节——我什至不确定这是正确的方法;如果有人有更简单的解决方法,我会全力以赴。



Edit:This can be reproduced with bog-standard CSS :hover, but here's a quick repro for reference.

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

If you mouse over either of the boxes, it will get a blue background, which I want. But if you tap on either of the boxes, it will also get a blue background, which is the thing I'm trying to prevent.

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

I've also posted a sample herethat does the above and also hooks jQuery's mouse events. You can use it to see that tap events will also fire mouseenter, mousemoveand mouseleave.

我还在此处发布了一个示例,该示例执行上述操作并挂钩 jQuery 的鼠标事件。您可以使用它来查看点击事件也将触发mouseenter,mousemovemouseleave

回答by MacFreek

I take it from your question that your hover effect changes the content of your page. In that case, my advice is to:

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

  • Add hover effects on touchstartand mouseenter.
  • Remove hover effects on mouseleave, touchmoveand click.
  • touchstart和上添加悬停效果mouseenter
  • 移除mouseleavetouchmove和上的悬停效果click

Alternatively, you can edit your page that there is no content change.

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

Background

背景

In order to simulate a mouse, browsers such as Webkit mobile fire the following events if a user touches and releases a finger on touch screen (like iPad) (source: Touch And Mouseon html5rocks.com):

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

  1. touchstart
  2. touchmove
  3. touchend
  4. 300ms delay, where the browser makes sure this is a single tap, not a double tap
  5. mouseover
  6. mouseenter
    • Note: If a mouseover, mouseenteror mousemoveevent changes the page content, the following events are never fired.
  7. mousemove
  8. mousedown
  9. mouseup
  10. click
  1. touchstart
  2. touchmove
  3. touchend
  4. 300 毫秒延迟,浏览器确保这是单击而不是双击
  5. mouseover
  6. mouseenter
    • 注意:如果mouseover,mouseentermousemove事件更改了页面内容,则永远不会触发以下事件。
  7. mousemove
  8. mousedown
  9. mouseup
  10. click

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

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

What's worse, if a mouseover event changes the page content, the click event is never fired, as explained on Safari Web Content Guide - Handling Events, in particular figure 6.4 in One-Finger Events. What exactly a "content change" is, will depend on browser and version. I've found that for iOS 7.0, a change in background color is not (or no longer?) a content change.

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

Solution Explained

解决方案说明

To recap:

回顾一下:

  • Add hover effects on touchstartand mouseenter.
  • Remove hover effects on mouseleave, touchmoveand click.
  • touchstart和上添加悬停效果mouseenter
  • 移除mouseleavetouchmove和上的悬停效果click

Note that there is no action on touchend!

请注意,没有对touchend!

This clearly works for mouse events: mouseenterand mouseleave(slightly improved versions of mouseoverand mouseout) are fired, and add and remove the hover.

这显然适用于鼠标事件:mouseentermouseleave(略有改善的版本mouseovermouseout)被解雇,并添加和删除悬停。

If the user actually clicks a link, the hover effect is also removed. This ensure that it is removed if the user presses the back button in the web browser.

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

This also works for touch events: on touchstart the hover effect is added. It is '''not''' removed on touchend. It is added again on mouseenter, and since this causes no content changes (it was already added), the clickevent is also fired, and the link is followed without the need for the user to click again!

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

The 300ms delay that a browser has between a touchstartevent and clickis actually put in good use because the hover effect will be shown during this short time.

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

If the user decides to cancel the click, a move of the finger will do so just as normal. Normally, this is a problem since no mouseleaveevent is fired, and the hover effect remains in place. Thankfully, this can easily be fixed by removing the hover effect on touchmove.

如果用户决定取消点击,手指的移动会像往常一样。通常,这是一个问题,因为没有mouseleave触发任何事件,并且悬停效果仍然存在。幸运的是,这可以通过移除 上的悬停效果轻松解决touchmove

That's it!

就是这样!

Note that it is possible to remove the 300ms delay, for example using the FastClick library, but this is out of scope for this question.

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

Alternative Solutions

替代解决方案

I've found the following problems with the following alternatives:

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

  • browser detection:Extremely prone to errors. Assumes that a device has either mouse or touch, while a combination of both will become more and more common when touch displays prolifirate.
  • CSS media detection:The only CSS-only solution I'm aware of. Still prone to errors, and still assumes that a device has either mouse or touch, while both are possible.
  • Emulate the click event in touchend:This will incorrectly follow the link, even if the user only wanted to scroll or zoom, without the intention of actually clicking the link.
  • Use a variable to suppress mouse events:This set a variable in touchendthat is used as a if-condition in subsequent mouse events to prevents state changes at that point in time. The variable is reset in the click event. See Walter Roman's answer on this page. This is a decent solution if you really don't want a hover effect on touch interfaces. Unfortunately, this does not work if a touchendis fired for another reason and no click event is fired (e.g. the user scrolled or zoomed), and is subsequently trying to following the link with a mouse (i.e on a device with both mouse and touch interface).
  • 浏览器检测:极易出错。假设设备具有鼠标或触摸功能,而当触摸显示器激增时,两者的组合将变得越来越普遍。
  • CSS 媒体检测:我所知道的唯一纯 CSS 解决方案。仍然容易出错,并且仍然假设设备具有鼠标或触摸功能,而两者都是可能的。
  • 在 中模拟点击事件touchend这将错误地跟随链接,即使用户只想滚动或缩放,而无意实际点击链接。
  • 使用变量来抑制鼠标事件:这会设置一个变量touchend,用作后续鼠标事件中的 if 条件,以防止该时间点的状态更改。该变量在点击事件中被重置。请参阅本页上 Walter Roman 的回答。如果您真的不想在触摸界面上出现悬停效果,这是一个不错的解决方案。不幸的是,如果 atouchend由于其他原因被触发并且没有触发点击事件(例如用户滚动或缩放),并且随后尝试使用鼠标跟踪链接(即在具有鼠标和触摸界面的设备上),则这不起作用)。

Further Reading

进一步阅读

回答by nnnnnn

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

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

Maybe don't think of it so much as suppressing hover effects for touchscreens, but as adding hover effects for mouse events?

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

If you want to keep the :hovereffects in your CSS you could specify different styles for different media:

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

@media screen { /* hover styles here */ } 

@media handheld { /* non-hover styles here */ }

Except that unfortunately there are plenty of mobile devices that ignore this and just use the screen rules. Fortunately a lot of newer mobile/tablet browsers do support some fancier media queries:

不幸的是,有很多移动设备忽略了这一点,只使用屏幕规则。幸运的是,许多较新的移动/平板电脑浏览器确实支持一些更高级的媒体查询:

@media screen and (max-width:800px) { /* non-hover styles here */ }

So even if the "screen" or "handheld" part is ignored the "max-width" will do the trick for you. You could just assume that anything with a screen smaller than 800 pixels must be a tablet or phone, and not use hover effects. For the rare users who are using a mouse on a low resolution device they wouldn't see the hover effects but your site would be fine otherwise.

因此,即使忽略“屏幕”或“手持设备”部分,“最大宽度”也会为您解决问题。你可以假设屏幕小于 800 像素的任何东西都必须是平板电脑或手机,而不是使用悬停效果。对于在低分辨率设备上使用鼠标的极少数用户,他们不会看到悬停效果,但您的网站在其他情况下会很好。

Further reading on media queries? There are plenty of articles about this online - here is one: http://www.alistapart.com/articles/return-of-the-mobile-stylesheet

进一步阅读媒体查询?网上有很多关于这个的文章 - 这里是一篇:http: //www.alistapart.com/articles/return-of-the-mobile-stylesheet

If you shift the hover effects out of your CSS and apply them with JavaScript then you could bind specifically to mouse events, and/or again you could just make some assumptions just based on screen size with the worst-case "problem" being that some user who is using a mouse misses out on the hover effects.

如果您将悬停效果从 CSS 中移出并使用 JavaScript 应用它们,那么您可以专门绑定到鼠标事件,和/或您可以再次仅根据屏幕大小做出一些假设,最坏的“问题”是一些使用鼠标的用户会错过悬停效果。

回答by Walter Roman

I wrote the following JS for a recent project, which was a desktop/mobile/tablet site that has hover effects that shouldn't appear on-touch.

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

The mobileNoHoverStatemodule below has a variable preventMouseover(initially declared as false), that is set to truewhen a user fires the touchstartevent on an element, $target.

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

preventMouseoveris then being set back to falsewhenever the mouseoverevent is fired, which allows the site to work as intended if a user is using both their touchscreen and mouse.

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

We know that mouseoveris being triggered after touchstartbecause of the order that they are being declared within init.

我们知道由于它们在 中声明的顺序而mouseover被触发。touchstartinit

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

The module is then instantiated further down the line:

然后该模块被进一步实例化:

mobileNoHoverState.init();

回答by flintinatux

I really wanted a pure csssolution to this myself, since sprinkling a weighty javascript solution around all of my views seemed like an unpleasant option. Finally found the @media.hoverquery, which can detect "whether the primary input mechanism allows the user to hover over elements." This avoids touch devices where "hovering" is more of an emulated action than a direct capability of the input device.

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

So for example, if I have a link:

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

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

Then I can safely style it to only :hoverwhen the device easily supports it with this css:

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

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

While most modern browsers support interaction media feature queries, some popular browsers such as IE and Firefoxdo not. In my case this works fine, since I only intended to support Chrome on desktop and Chrome and Safari on mobile.

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

回答by Bnaya

My solution is to add hover-active css class to the HTML tag, and use it on the beginning of all the CSS selectors with :hover and remove that class on the first touchstart event.

我的解决方案是将悬停活动 css 类添加到 HTML 标记,并在所有 CSS 选择器的开头使用 :hover 并在第一个 touchstart 事件中删除该类。

http://codepen.io/Bnaya/pen/EoJlb

http://codepen.io/Bnaya/pen/EoJlb

JS:

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:

<html class="hover-active">

CSS:

CSS:

.hover-active .mybutton:hover {
    box-shadow: 1px 1px 1px #000;
}

回答by Daft

Right, I jst had a similar problem but managed to fix it with media queries and simple CSS. I'm sure I'm breaking some rules here, but it's working for me.

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

I basically had to take a massive application someone made, and make it responsive. They used jQueryUI and asked me not to tamper with any of their jQuery, so I was restricted to using CSS alone.

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

When I pressed one of their buttons in touchscreen mode, the hover effect woudld fire for a second before the button's action took effect. Here's how I fixed it.

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

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

       #buttonOne{
            height: 44px;
        }


        #buttonOne:hover{
            display:none;
        }
}   

回答by Stein G. Strindhaug

What I've done to solve the same problem is to have a feature detection (I use something like this code), seeing if onTouchMove is defined, and if so I add the css class "touchMode" to the body, else i add "desktopMode".

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

Then every time some style effect only applies to a touch device, or only to a desktop the css rule is prepended with the appropriate class:

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

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

Edit: This strategy of course adds a few extra characters to your css, so If you're concerned about css size, you could search for the touchMode and desktopMode definitons and put them into different files, so you can serve optimized css for each device type; or you could change the class names to something much shorter before going to prod.

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

回答by Stanislav Berkov

In my project we solved this issue using https://www.npmjs.com/package/postcss-hover-prefixand https://modernizr.com/First we post-process output css files with postcss-hover-prefix. It adds .no-touchfor all css hoverrules.

在我的项目中,我们使用https://www.npmjs.com/package/postcss-hover-prefixhttps://modernizr.com/解决了这个问题, 首先我们使用postcss-hover-prefix. 它添加.no-touch了所有 csshover规则。

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

css

a.text-primary:hover {
  color: #62686d;
}

becomes

变成

.no-touch a.text-primary:hover {
  color: #62686d;
}

At runtime Modernizrautomatically adds css classes to htmltag like this

在运行时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">

Such post-processing of css plus Modernizr disables hover for touch devices and enables for others. In fact this approach was inspired by Bootstrap 4, how they solve the same issue: https://v4-alpha.getbootstrap.com/getting-started/browsers-devices/#sticky-hoverfocus-on-mobile

css 和 Modernizr 的这种后处理会禁用触摸设备的悬停并启用其他设备。事实上,这种方法的灵感来自 Bootstrap 4,他们如何解决同样的问题:https: //v4-alpha.getbootstrap.com/getting-started/browsers-devices/#sticky-hoverfocus-on-mobile

回答by Simon Dragsb?k

Iv'd found 2 solutions to the problem, which its implied that you detect touch with modernizr or something else and set a touch class on the html element.

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

This is good but not supportedvery well:

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

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

But this has a very good support:

但这有很好的支持

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

Works flawless for me, it makes all the hover effects be like when you have a touch on a button it will light up but not end up buggy as the initial hover effect for mouse events.

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

Detecting touch from no-touch devices i think modernizr has done the best job:

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

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

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

EDIT

编辑

I found a better and simpler solution to this issue

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

How to determine if the client is a touch device

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

回答by Said Kholov

You can trigger the mouseLeaveevent whenever you touch an element on touchscreen. Here is a solution for all <a>tags:

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