jQuery iPad/iPhone 悬停问题导致用户双击链接
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3038898/
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
iPad/iPhone hover problem causes the user to double click a link
提问by Francesco
I have some websites I built times ago, that use jquery mouse events...I just got an ipad and i noticed that all the mouse over events are translated in clicks...so for instance i have to do two clicks instead of one..(the first hover, than the actual click)
我有一些我以前建立的网站,它们使用 jquery 鼠标事件......我刚买了一个 ipad,我注意到所有的鼠标悬停事件都被翻译成点击......所以例如我必须点击两次而不是一次..(第一次悬停,而不是实际点击)
is there a workaround ready to solve this? maybe a jquery command i shoudl have used instead of mouseover/out etc.. thanks!
是否有解决方法可以解决这个问题?也许我应该使用 jquery 命令而不是鼠标悬停/退出等。谢谢!
回答by cduruk
Haven't tested this fully but since iOS fires touch events, this could work, assuming you are in a jQuery setting.
尚未对此进行全面测试,但由于 iOS 会触发触摸事件,因此假设您处于 jQuery 设置中,这可能会起作用。
$('a').on('click touchend', function(e) {
var el = $(this);
var link = el.attr('href');
window.location = link;
});
The idea is that Mobile WebKit fires a touchend
event at the end of a tap so we listen for that and then redirect the browser as soon as a touchend
event has been fired on a link.
这个想法是 Mobile WebKittouchend
在点击结束时触发一个事件,所以我们会监听它,然后一旦touchend
在链接上触发了一个事件就重定向浏览器。
回答by MacFreek
It is not entirely clear what your question is, but if you just want to eliminate the double click, while retaining the hover effect for the mouse, my advice is to:
目前尚不清楚您的问题是什么,但如果您只想消除双击,同时保留鼠标的悬停效果,我的建议是:
- Add hover effects on
touchstart
andmouseenter
. - Remove hover effects on
mouseleave
,touchmove
andclick
.
- 在
touchstart
和上添加悬停效果mouseenter
。 - 移除
mouseleave
、touchmove
和上的悬停效果click
。
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):
touchstart
touchmove
touchend
- 300ms delay, where the browser makes sure this is a single tap, not a double tap
mouseover
mouseenter
- Note: If a
mouseover
,mouseenter
ormousemove
event changes the page content, the following events are never fired.
- Note: If a
mousemove
mousedown
mouseup
click
touchstart
touchmove
touchend
- 300 毫秒延迟,浏览器确保这是单击而不是双击
mouseover
mouseenter
- 注意:如果
mouseover
,mouseenter
或mousemove
事件更改了页面内容,则永远不会触发以下事件。
- 注意:如果
mousemove
mousedown
mouseup
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
touchstart
andmouseenter
. - Remove hover effects on
mouseleave
,touchmove
andclick
.
- 在
touchstart
和上添加悬停效果mouseenter
。 - 移除
mouseleave
、touchmove
和上的悬停效果click
。
Note that there is no action on touchend
!
请注意,没有对touchend
!
This clearly works for mouse events: mouseenter
and mouseleave
(slightly improved versions of mouseover
and mouseout
) are fired, and add and remove the hover.
这显然适用于鼠标事件:mouseenter
和mouseleave
(略有改善的版本mouseover
和mouseout
)被解雇,并添加和删除悬停。
If the user actually click
s 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 click
event 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 touchstart
event and click
is 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 mouseleave
event 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
touchend
that 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. This is a decent solution if you really don't want a hover effect on touch interfaces. Unfortunately, this does not work if atouchend
is 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 条件,以防止该时间点的状态更改。该变量在点击事件中被重置。如果您真的不想在触摸界面上出现悬停效果,这是一个不错的解决方案。不幸的是,如果 atouchend
由于其他原因被触发并且没有触发点击事件(例如用户滚动或缩放),并且随后尝试使用鼠标跟踪链接(即在具有鼠标和触摸界面的设备上),则这不起作用)。
Further Reading
进一步阅读
- http://jsfiddle.net/macfreek/24Z5M/. Test the above solution for yourself in this sandbox.
- http://www.macfreek.nl/memory/Touch_and_mouse_with_hover_effects_in_a_web_browser. This same answer, with a bit more background.
- http://www.html5rocks.com/en/mobile/touchandmouse/. Great background article on html5rocks.com about touch and mouse in general.
- https://developer.apple.com/library/ios/DOCUMENTATION/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html. Safari Web Content Guide - Handling Events. See in particular figure 6.4, which explains that no further events are fired after a content change during a
mouseover
ormousemove
event.
- http://jsfiddle.net/macfreek/24Z5M/。在此沙箱中为自己测试上述解决方案。
- http://www.macfreek.nl/memory/Touch_and_mouse_with_hover_effects_in_a_web_browser。同样的答案,有更多的背景。
- http://www.html5rocks.com/en/mobile/touchandmouse/。html5rocks.com 上关于触摸和鼠标的优秀背景文章。
- https://developer.apple.com/library/ios/DOCUMENTATION/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html。Safari Web 内容指南 - 处理事件。特别参见图 6.4,它解释了在
mouseover
或mousemove
事件期间内容更改后不会触发其他事件。
See also iPad/iPhone double click problemand Disable hover effects on mobile browsers.
回答by Pavel Bogatinov
Seems there is a CSS solution after all. The reason Safari waits for a second touch is because of the background image (or elements) you usually assign on the :hover event. If there is none to be shown - you won't have any problems. The solution is to target iOS platform with secondary CSS file (or style in case of a JS approach) which overrides :hover background to inherit for example and keep hidden the elements you were going to display on mouse over:
毕竟似乎有一个 CSS 解决方案。Safari 等待第二次触摸的原因是您通常在 :hover 事件上分配的背景图像(或元素)。如果没有显示 - 你不会有任何问题。解决方案是使用辅助 CSS 文件(或 JS 方法的样式)定位 iOS 平台,该文件覆盖 :hover 背景以继承例如并隐藏您要在鼠标悬停时显示的元素:
Here is an example CSS and HTML - a product block with a starred label on mouse over:
这是一个示例 CSS 和 HTML - 鼠标悬停时带有星标标签的产品块:
HTML:
HTML:
<a href="#" class="s"><span class="s-star"></span>Some text here</a>
CSS:
CSS:
.s {
background: url(some-image.png) no-repeat 0 0;
}
.s:hover {
background: url(some-image-r.png) no-repeat 0 0;
}
.s-star {
background: url(star.png) no-repeat 0 0;
height: 56px;
position: absolute;
width: 72px;
display:none;
}
.s:hover .s-star {
display:block;
}
Solution (secondary CSS):
解决方案(辅助 CSS):
/* CSS */
/* Keep hovers the same or hidden */
.s:hover {
background:inherit;
}
.s:hover .s-star {
display:none;
}
回答by Adauto
What worked for me is what others here have already said:
对我有用的是这里的其他人已经说过的:
Don't show/hide elements on hover or mousemove (which is the event in my case).
不要在悬停或鼠标移动时显示/隐藏元素(这就是我的情况)。
Here's what Apple says (https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html):
A clickable element is a link, form element, image map area, or any other element with mousemove, mousedown, mouseup, or onclick handlers
If the user taps a clickable element, events arrive in this order: mouseover, mousemove, mousedown, mouseup, and click. Also, if the contents of the page changes on the mousemove event, no subsequent events in the sequence are sent.This behavior allows the user to tap in the new content.
可点击元素是链接、表单元素、图像映射区域或任何其他具有 mousemove、mousedown、mouseup 或 onclick 处理程序的元素
如果用户点击一个可点击元素,事件会按以下顺序到达:mouseover、mousemove、mousedown、mouseup 和 click。此外,如果页面内容在 mousemove 事件上发生变化,则不会发送序列中的后续事件。此行为允许用户点击新内容。
So, you could use @woop's solution: detect the userAgent, check if it's and iOS device and then bind the event. I ended up using this technique because it suits my needs and it makes more sense do not bind hover events when you don't want it.
所以,你可以使用@woop 的解决方案:检测 userAgent,检查它是否和 iOS 设备,然后绑定事件。我最终使用了这种技术,因为它适合我的需求,而且在您不想要时不绑定悬停事件更有意义。
But... if you don't wanna mess with userAgents and still hide/show elements on hover/mousemove, i found out you can do so by using native javascript, like this:
但是......如果你不想弄乱 userAgents 并且仍然在悬停/鼠标移动时隐藏/显示元素,我发现你可以通过使用原生 javascript 来做到这一点,如下所示:
$("body").on("mouseover", function() {
document.getElementsByTagName("my-class")[0].style.display = 'block'; //show element
document.querySelector(".my-selector div").style.display = 'none'; // hide element
});
This will work on the Desktop version and will do nothing on the mobile version.
这将适用于桌面版本,而不会在移动版本上执行任何操作。
And for a little more compatibility...
为了更多的兼容性......
$("body").on("mouseover", function() {
if (document.getElementsByTagName && document.querySelector) { // check compatibility
document.getElementsByTagName("my-class")[0].style.display = 'block'; //show element
document.querySelector(".my-selector div").style.display = 'none'; // hide element
} else {
$(".my-class").show();
$(".my-selector div").hide();
}
});
回答by Jsonras
No need to make overcomplicated.
没必要搞得太复杂。
$('a').on('touchend', function() {
$(this).click();
});
回答by woop
cduruk's solution was quite effective, but caused problems on a few parts of my site. Because I was already using jQuery to add the CSS hover class, the easiest solution was to simply not add the CSS hover class on mobile devices (or more precisely, to ONLY add it when NOT on a mobile device).
cduruk 的解决方案非常有效,但在我网站的一些部分引起了问题。因为我已经在使用 jQuery 添加 CSS 悬停类,所以最简单的解决方案是在移动设备上不添加 CSS 悬停类(或者更准确地说,仅在不在移动设备上时添加它)。
Here was the general idea:
这是一般的想法:
var device = navigator.userAgent.toLowerCase();
var ios = device.match(/(iphone|ipod|ipad)/);
if (!(ios)) {
$(".portfolio-style").hover(
function(){
$(this).stop().animate({opacity: 1}, 100);
$(this).addClass("portfolio-red-text");
},
function(){
$(this).stop().animate({opacity: 0.85}, 100);
$(this).removeClass("portfolio-red-text");
}
);
}
*code reduced for illustrative purposes
*为了说明目的减少了代码
回答by Paul Irish
I think it'd be wise to try mouseenter
in place of mouseover
. It's what's used internally when binding to .hover(fn,fn)
and is generally what you want.
我认为尝试mouseenter
代替mouseover
. 它是绑定到内部时使用的.hover(fn,fn)
,通常是您想要的。
回答by albert
I "think" that your links have no onmouseover event, where 1 tap activates onmouseover and the double tap activates the link. but idk. I don't have an iPad. I think ya gotta use gesture/touch events.
我“认为”您的链接没有 onmouseover 事件,其中 1 次点击激活 onmouseover,双击激活链接。但我知道。我没有 iPad。我认为你必须使用手势/触摸事件。
回答by user56reinstatemonica8
I had the following problems with the existing solutions, and found something that seems to solve all of them. This assumes you're aiming for something cross browser, cross device, and don't want device sniffing.
我在现有解决方案中遇到了以下问题,并发现了一些似乎可以解决所有问题的方法。这假设您的目标是跨浏览器、跨设备,并且不希望设备嗅探。
The problems this solves
这解决的问题
Using just touchstart
or touchend
:
仅使用touchstart
或touchend
:
- Causes the event to fire when people are trying to scroll past the contentand just happened to have their finger over this element when they starting swiping - triggering the action unexpectedly.
- May cause the event to fire on longpress, similar to right click on desktop. For example, if your click event goes to URL X, and the user longpresses to open X in a new tab, the user will be confused to find X open in both tabs. On some browsers (e.g. iPhone) it may even prevent the long press menu from appearing.
- 当人们试图滚动内容时触发事件,并且当他们开始滑动时碰巧将手指放在此元素上 - 意外触发操作。
- 可能会导致在 longpress上触发事件,类似于在桌面上右键单击。例如,如果您的点击事件转到 URL X,并且用户长按以在新选项卡中打开 X,则用户会混淆发现 X 在两个选项卡中都打开。在某些浏览器(例如 iPhone)上,它甚至可能会阻止长按菜单出现。
Triggering mouseover
events on touchstart
and mouseout
on touchmove
has less serious consequences, but does interfere with the usual browser behaviour, for example:
触发mouseover
的事件touchstart
,并mouseout
在touchmove
有那么严重的后果,但与通常的浏览器行为干扰,例如:
- A long press would trigger a mouseover that never ends.
- Many Android browsers treat the location of the finger on
touchstart
like amouseover
, which ismouseout
ed on the nexttouchstart
. One way to see mouseover content in Android is therefore to touch the area of interest and wiggle your finger, scrolling the page slightly. Treatingtouchmove
asmouseout
breaks this.
- 长按会触发永远不会结束的鼠标悬停。
- 许多 Android 浏览器将手指的位置视为
touchstart
amouseover
,即mouseout
ed on nexttouchstart
。因此,在 Android 中查看鼠标悬停内容的一种方法是触摸感兴趣的区域并摆动手指,稍微滚动页面。治疗touchmove
作为mouseout
打破了这种。
The solution
解决方案
In theory, you could just add a flag with touchmove
, but iPhones trigger touchmove even if there's no movement. In theory, you could just compare the touchstart
and touchend
event pageX
and pageY
but on iPhones, there's no touchend
pageX
or pageY
.
理论上,您可以只添加一个带有 的标志touchmove
,但是即使没有移动,iPhone 也会触发 touchmove。理论上,您可以只比较touchstart
andtouchend
事件pageX
,pageY
但在 iPhone 上,没有touchend
pageX
orpageY
。
So unfortunately to cover all bases it does end up a little more complicated.
所以不幸的是,要涵盖所有基础,它最终会变得更复杂一些。
$el.on('touchstart', function(e){
$el.data('tstartE', e);
if(event.originalEvent.targetTouches){
// store values, not reference, since touch obj will change
var touch = e.originalEvent.targetTouches[0];
$el.data('tstartT',{ clientX: touch.clientX, clientY: touch.clientY } );
}
});
$el.on('touchmove', function(e){
if(event.originalEvent.targetTouches){
$el.data('tstartM', event.originalEvent.targetTouches[0]);
}
});
$el.on('click touchend', function(e){
var oldE = $el.data('tstartE');
if( oldE && oldE.timeStamp + 1000 < e.timeStamp ) {
$el.data('tstartE',false);
return;
}
if( $el.data('iosTouchM') && $el.data('tstartT') ){
var start = $el.data('tstartT'), end = $el.data('tstartM');
if( start.clientX != end.clientX || start.clientY != end.clientY ){
$el.data('tstartT', false);
$el.data('tstartM', false);
$el.data('tstartE',false);
return;
}
}
$el.data('tstartE',false);
In theory, there are ways to get the exact time used for a longpress instead of just using 1000 as an approximation, but in practice it's not that simple and it's best to use a reasonable proxy.
从理论上讲,有一些方法可以获取 longpress 使用的确切时间,而不是仅使用 1000 作为近似值,但实际上并没有那么简单,最好使用合理的 proxy。
回答by Mad Dog Cadogen
MacFreak's answer was extremely helpful to me. Here's some hands-on code in case it helps you.
MacFreak 的回答对我非常有帮助。这是一些动手代码,以防它对您有所帮助。
PROBLEM- applying touchendmeans every time you scroll your finger over an element, it responds as if you've pressed it, even if you were just trying to scroll past.
问题- 应用touchend意味着每次你在一个元素上滚动你的手指时,它的响应就像你按下它一样,即使你只是试图滚动过去。
I'm creating an effect with jQuery which fades up a line under some buttons to "highlight" the hovered button. I do not want this to mean you have to press the button twice on touch devices to follow the link.
我正在使用 jQuery 创建一个效果,它在一些按钮下淡出一条线以“突出显示”悬停的按钮。我不希望这意味着您必须在触摸设备上按两次按钮才能点击链接。
Here are the buttons:
以下是按钮:
<a class="menu_button" href="#">
<div class="menu_underline"></div>
</a>
I want the "menu_underline" div to fade up on mouseover and fade out on mouseout. BUT I want touch devices to be able to follow the link on one single click, not two.
我希望“menu_underline”div 在鼠标悬停时淡出并在鼠标悬停时淡出。但是我希望触摸设备能够通过单击而不是两次单击来跟随链接。
SOLUTION- Here's the jQuery to make it work:
解决方案- 这是使其工作的 jQuery:
//Mouse Enter
$('.menu_button').bind('touchstart mouseenter', function(){
$(this).find(".menu_underline").fadeIn();
});
//Mouse Out
$('.menu_button').bind('mouseleave touchmove click', function(){
$(this).find(".menu_underline").fadeOut();
});
Many thanks for your help on this MacFreak.
非常感谢您对 MacFreak 的帮助。