iPad/iPhone悬停问题导致用户需要双击链接。

132
我以前建的一些网站使用了jquery鼠标事件......我刚刚买了一个iPad,发现所有的鼠标悬停事件都被转换为点击事件...所以例如我需要做两个点击而不是一个...(先是悬停,然后才是实际点击)
是否有一种解决方法可以解决这个问题?也许有一个我应该使用而不是mouseover/out等的jquery命令.. 谢谢!

1
你的事件绑定在什么上面?例如,onclick事件应该能正常工作... onmouseover、onmouseout和CSS的:hover是有点难处理的,因为触摸屏没有"hover"可用。你有代码示例吗? - scunliffe
我建议您重新思考界面设计(如果可能的话)。iPad / iPhone上的交互方式与PC上不完全相同,因此最好让您的网站感觉像是针对iPad / iPhone / 其他具有类似多点触控机制的触摸设备编写的。这只是一个想法。 - jer
我同意“jer”的观点。这是一个奇怪的问题,我个人认为解决方案不是“变通”。我认为将桌面浏览器上的“鼠标悬停”翻译成触摸屏浏览器上的“手指轻触”是有道理的。如果您同意这种翻译,但想要一次轻触而不是两次,则可能需要对iPad事件(例如“touchstart”)进行功能检测并更改事件处理程序。也许将代码提取到一个jquery插件中,“触摸或点击”类似的功能根据特性以不同的方式触发,但在我看来似乎是针对您的网站/应用程序的。 - Andy Atkinson
1
我认为这种翻译实际上是一种功能。如果您设置了悬停事件,那么肯定有一些实用性可见。单击会显示悬停元素,第二次点击会跟随链接“后面”的悬停。 - Aaron
27个回答

207

我没有完全测试过这个方法,但是由于iOS会触发touch事件,所以这个方法可能可行,假设你正在使用jQuery。

$('a').on('click touchend', function(e) {
    var el = $(this);
    var link = el.attr('href');
    window.location = link;
});

Mobile WebKit在轻击结束时会触发touchend事件,我们监听该事件,一旦链接被触发了touchend事件,就立即将浏览器重定向。


3
耐心等待,学徒。S.O.说需要24小时才能生效。 - Andrew Hedges
28
好的,这解决了最初的问题,但却产生了一个新问题:当滑动经过被点击的区域时。这不是一个解决方案。 - luksak
2
对我来说这感觉像是一种hack,苹果已经记录了触摸事件(http://developer.apple.com/library/iOS/#documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)的行为。 这篇文章介绍如何确保您的网站适应这种行为-> http://www.nczonline.net/blog/2012/07/05/ios-has-a-hover-problem/ 基本上,不要用JS或CSS在悬停时隐藏/显示内容,就不会有双击问题。 - David Rogers
10
这个有缺陷。如果你点击一个带有 target="_blank" 属性的链接,它会在当前窗口和新窗口中同时打开。 - rybo111
2
这太强了。它会破坏任何触发 JavaScript 事件(如 Ajax 调用)的链接。这在 Web 框架中很常见(例如 Rails 使用它),它将破坏 Ajax 调用。 - Sir Celsius
显示剩余9条评论

39

虽然不是很清楚您的问题,但如果您只是想在保留鼠标悬停效果的同时消除双击,我的建议是:

  • touchstartmouseenter 上添加悬停效果。
  • mouseleavetouchmoveclick 上移除悬停效果。

背景

为了模拟鼠标,像 Webkit 移动端这样的浏览器会在用户触摸并释放触摸屏幕(例如 iPad)时触发以下事件(来源:Touch And Mouse on html5rocks.com):

  1. touchstart
  2. touchmove
  3. touchend
  4. 300 毫秒的延迟,确保这是单击,而不是双击
  5. mouseover
  6. mouseenter
    • 注意:如果 mouseovermouseentermousemove 事件更改页面内容,则不会触发以下事件。
  7. mousemove
  8. mousedown
  9. mouseup
  10. click

似乎没有办法简单地告诉网页浏览器跳过鼠标事件。

更糟糕的是,如果鼠标悬停事件改变了页面内容,那么点击事件就永远不会被触发。具体来说,请参见Safari Web内容指南-处理事件中的第6.4图,特别是“单指事件”。什么是“内容更改”将取决于浏览器和版本。 我发现,对于iOS 7.0,背景颜色的更改不是(或不再是)内容更改。
解决方案说明:
- 在touchstart和mouseenter上添加hover效果 - 在mouseleave、touchmove和click上删除hover效果 - 注意,touchend上没有任何操作!
这显然适用于鼠标事件:mouseenter和mouseleave(稍微改进的mouseover和mouseout)被触发,并添加和删除hover。如果用户实际点击链接,则还会删除悬停效果。这确保了如果用户在web浏览器中按下后退按钮,则已删除。
这也适用于触摸事件:在touchstart上添加悬停效果。它在touchend上不会被删除。它会在mouseenter上重新添加,由于这不会导致内容更改(已经添加了),因此也触发了click事件,无需用户再次单击即可跟随链接!浏览器在touchstart事件和click之间的300ms延迟实际上是有用的,因为在这段短时间内会显示悬停效果。如果用户决定取消点击操作,就像平常一样移动手指即可。通常情况下,这是一个问题,因为不会触发mouseleave事件,而悬停效果仍然存在。幸运的是,可以通过在touchmove上删除悬停效果来轻松解决这个问题。
就是这样!
请注意,可以通过使用FastClick库等方法来消除300毫秒的延迟,但这超出了本问题的范围。
备选方案
我发现以下备选方案存在以下问题:
- 浏览器检测:极易出错。假设设备具有鼠标或触摸屏,而在触摸显示屏普及的情况下,两者的组合将变得越来越普遍。 - CSS媒体检测:我所知道的唯一基于CSS的解决方案。仍然容易出错,并且仍然假设设备具有鼠标或触摸屏,而两者都是可能的。 - 在touchend中模拟点击事件:即使用户只想滚动或缩放,而没有真正点击链接的意图,也会错误地跟随链接。 - 使用变量抑制鼠标事件:在touchend中设置一个变量,该变量用作后续鼠标事件的if条件,在此时防止状态更改。单击事件会重置该变量。如果您确实不希望在触摸界面上出现悬停效果,则这是一个不错的解决方案。不幸的是,如果由于其他原因触发了touchend并且没有触发单击事件(例如用户滚动或缩放),然后尝试在鼠标上(即在具有鼠标和触摸界面的设备上)跟随链接,这就无法正常工作。
进一步阅读

另请参见iPad/iPhone双击问题在移动浏览器上禁用悬停效果


2
似乎这是那些想要更多解释而不仅仅是问题解决方案的最佳解释。谢谢。 - antiplayer
特别感谢提供iOS7中背景颜色信息的人。我花了一些时间试图弄清楚这是什么时候开始生效的。 - staffang

20

看起来还是有CSS解决方案的。Safari等待第二次触摸的原因是由于通常在:hover事件上分配的背景图片(或元素)。如果没有要显示的内容,那么你就不会有任何问题。 解决方法是使用辅助CSS文件(或JS方法中的样式)针对iOS平台进行定位,覆盖:hover背景为inherit等,并保持隐藏你要在鼠标悬停时显示的元素:

这里是一个CSS和HTML示例-带有鼠标悬停星标标签的产品块:

HTML:

<a href="#" class="s"><span class="s-star"></span>Some text here</a>

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

解决方案(次要的CSS):

/* CSS */
/* Keep hovers the same or hidden */
.s:hover {
   background:inherit;
}
.s:hover .s-star {
   display:none;
}

1
一个关于iOS :hover行为的不错概述。 - hurrymaplelad
这个答案是针对背景图片的,但你有没有简单透明度变化的解决方案?这个解决方案对此不起作用。 - Ian Steffy

6

不需要过于复杂。

$('a').on('touchend', function() {
    $(this).click();
});

5
我所使用的方法是其他各位已经提到过的:

不要在hover或mousemove(在我的情况下)上显示/隐藏元素。

这是Apple的说法(https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html):

可点击的元素包括链接、表单元素、图像映射区域以及任何带有mousemove、mousedown、mouseup或onclick处理程序的元素;

如果用户点击可点击的元素,则事件按此顺序发生:mouseover、mousemove、mousedown、mouseup和click。 另外,如果页面的内容在mousemove事件上更改,则不会发送序列中的后续事件。 此行为允许用户在新内容中轻击。

因此,您可以使用@woop的解决方案:检测userAgent,检查它是否是iOS设备,然后绑定事件。 我最终使用了这种技术,因为它符合我的需求,并且当您不想绑定hover事件时,它更加合理。

但是... 如果您不想混合userAgents,而仍然想在hover/mousemove上隐藏/显示元素,我发现您可以使用本机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
});

这将适用于桌面版本,并在移动版本上不起作用。

为了更好的兼容性...

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

1
那句“如果在mousemove事件中页面内容发生变化,则不会发送后续事件”真的为我解决了问题。我有一个页面,标题链接需要双击,而内容中的按钮(带着所有愉悦的CSS3过渡效果)只需要单击。似乎标题链接有一个伪元素,在悬停时从opacity:0到opacity:1。删除该效果立即解决了问题,我找到了一个CSS解决方法,仍然可以获得我想要的外观。 - Fake Haak

4

我认为用mouseenter替换mouseover是明智的选择。在绑定到.hover(fn,fn)时,它是内部使用的,并且通常是您想要的。


3

cduruk的解决方案非常有效,但在我的网站某些部分引起了问题。因为我已经在使用jQuery来添加CSS hover类,最简单的解决方案是在移动设备上不添加CSS hover类(或更准确地说,在非移动设备上才添加)。

以下是基本思路:

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

*该示例代码仅供说明


1
这显然是答案。其他“解决方案”无法解决问题。iOS Safari 明显存在缺陷,认为我们想要先悬停。其他操作系统具有实际的悬停功能,当您悬停在项目上时会出现。 - Joshua Pack
这是唯一对我有效的方法。非常感谢! - tx291

3
我对现有解决方案存在以下问题,并找到了一些看起来可以解决所有问题的东西。这假定您正在寻求跨浏览器、跨设备的内容,而且不想进行设备嗅探。
这解决的问题:
仅使用touchstart或touchend会导致以下问题:
- 当人们试图滚动内容并碰巧在开始滑动时手指恰好放在此元素上时,会触发事件 - 意外地触发操作。 - 可能会导致事件在长时间按压时触发,类似于桌面上的右键单击。例如,如果您的单击事件转到URL X,并且用户长按以在新标签页中打开X,则用户会感到困惑,因为X在两个标签页中都打开了。在某些浏览器(例如iPhone)上,这甚至可能阻止长按菜单出现。
在touchstart上触发mouseover事件,在touchmove上触发mouseout事件具有较小的影响,但确实干扰了通常的浏览器行为,例如:
  • 长按会触发永不结束的鼠标悬停。
  • 许多 Android 浏览器将手指在 touchstart 上的位置视为 mouseover,并在下一个 touchstart 上进行 mouseout。因此,在 Android 中查看鼠标悬停内容的一种方法是触摸感兴趣的区域并晃动手指,稍微滚动页面。将 touchmove 视为 mouseout 会破坏这个功能。

解决方案

理论上,您可以通过 touchmove 添加标志,但 iPhones 即使没有移动也会触发 touchmove。理论上,您可以仅比较 touchstarttouchend 事件的 pageXpageY,但在 iPhones 上,没有 touchend pageXpageY

因此,不幸的是,为了覆盖所有情况,它最终变得有点复杂。

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

理论上,有获取长按所需的精确时间而不仅限于使用1000作为近似值的方法,但在实践中并不那么简单,最好使用合理的代理。


1

MacFreak的回答对我非常有帮助。以下是一些实际操作代码,希望能对您有所帮助。

问题 - 应用 touchend 意味着每次您用手指滚动元素时,它都会响应,即使您只是试图向下滚动。

我正在使用jQuery创建一种效果,通过淡入一个按钮下方的线条来“突出显示”悬停的按钮。我不希望这意味着您在触摸设备上必须按两次按钮才能跟随链接。

这些是按钮:

<a class="menu_button" href="#">
    <div class="menu_underline"></div>
</a>

我希望“menu_underline” div在鼠标悬停时淡入,鼠标移出时淡出。但是,我希望触摸设备能够在单击一次后跟随链接,而不是两次。 解决方案 - 这是使其工作的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();
});

非常感谢您在MacFreak上的帮助。

1

我刚刚发现,如果添加一个空的监听器,它就可以工作了。不要问我为什么,但我在iOS 9.3.2的iPhone和iPad上进行了测试,它可以正常工作。

if(/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream){
    var elements = document.getElementsByTagName('a');
    for(var i = 0; i < elements.length; i++){
        elements[i].addEventListener('touchend',function(){});
    }
}

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