是否可以强制忽略 iPhone/iPad 用户的 :hover 伪类?

发布于 2024-08-31 00:29:58 字数 745 浏览 9 评论 0原文

我的网站上有一些用 :hover 扩展的 css 菜单(没有 js),

这在 iDevices 上以半损坏的方式工作,例如点击将激活 :hover > 规则并展开菜单,但点击其他位置并不会删除 :hover。此外,如果元素内有一个被 :hover 编辑的链接,您必须点击两次才能激活该链接(第一次点击触发 :hover,第二次点击触发链接)。

通过绑定 touchstart 事件,我已经能够在 iPhone 上正常工作。

问题是,有时移动 safari 仍然选择从我的 touchstart 事件的 css 而不是触发 :hover 规则!

我知道这是问题所在,因为当我在 css 中手动禁用所有 :hover 规则时,移动 safari 工作得很好(但常规浏览器显然不再这样了)。

当用户在移动 safari 上时,有没有办法动态“取消”某些元素的 :hover 规则?

在此处查看并比较 iOS 行为:http://jsfiddle.net/74s35/3/ 注意:只有某些 css 属性会触发双击行为,例如 display:none;但不是背景:红色;或文字装饰:下划线;

I have some css menus on my site that expand with :hover (without js)

This works in a semi-broken way on iDevices, for example a tap will activate the :hover rule and expand the menu, but then tapping elsewhere doesn't remove the :hover. Also if there is a link inside the element that is :hover'ed, you have to tap twice to activate the link (first tap triggers :hover, second tap triggers link).

I've been able to make things work nicely on iphone by binding the touchstart event.

The problem is that sometimes mobile safari still chooses to trigger the :hover rule from the css instead of my touchstart events!

I know this is the problem because when I disable all the :hover rules manually in the css, mobile safari works great (but regular browsers obviously don't anymore).

Is there a way to dynamically "cancel" :hover rules for certain elements when the user is on mobile safari?

See and compare iOS behavior here: http://jsfiddle.net/74s35/3/
Note: that only some css properties trigger the two-click behavior, e.g. display:none; but not background: red; or text-decoration: underline;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

走过海棠暮 2024-09-07 00:29:58

我发现“:hover”在 iPhone/iPad Safari 中是不可预测的。有时点击元素会使该元素“:悬停”,而有时它会漂移到其他元素。

目前,我只在身体上上“无接触”课程。

<body class="yui3-skin-sam no-touch">
   ...
</body>

并将所有 CSS 规则与“:hover”放在“.no-touch”下面:

.no-touch my:hover{
   color: red;
}

在页面的某个位置,我有 javascript 从正文中删除 no-touch 类。

if ('ontouchstart' in document) {
    Y.one('body').removeClass('no-touch');
}

这看起来并不完美,但无论如何它都有效。

I found that ":hover" is unpredictable in iPhone/iPad Safari. Sometimes tap on element make that element ":hover", while sometimes it drifts to other elements.

For the time being, I just have a "no-touch" class at body.

<body class="yui3-skin-sam no-touch">
   ...
</body>

And have all CSS rules with ":hover" below ".no-touch":

.no-touch my:hover{
   color: red;
}

Somewhere in the page, I have javascript to remove no-touch class from body.

if ('ontouchstart' in document) {
    Y.one('body').removeClass('no-touch');
}

This doesn't look perfect, but it works anyway.

甜扑 2024-09-07 00:29:58

:hover 不是这里的问题。 iOS 版 Safari 遵循一个非常奇怪的规则。它首先触发 mouseovermousemove;如果在这些事件期间发生任何更改,“单击”和相关事件不会被触发:

iOS 中的触摸事件图

mouseentermouseleave 似乎包含在内,尽管图表中未指定它们。

如果您因这些事件而修改任何内容,则不会触发单击事件。这包括 DOM 树中更高层的东西。例如,这将阻止使用 jQuery 在您的网站上进行单击:

$(window).on('mousemove', function() {
    $('body').attr('rel', Math.random());
});

编辑:为了澄清起见,jQuery 的 hover 事件包括 mouseentermouseleave。如果内容发生更改,这些都会阻止点击

:hover isn't the issue here. Safari for iOS follows a very odd rule. It fires mouseover and mousemove first; if anything is changed during these events, 'click' and related events don't get fired:

Diagram of touch event in iOS

mouseenter and mouseleave appear to be included, though they're not specified in the chart.

If you modify anything as a result of these events, click events won't get fired. That includes something higher up in the DOM tree. For example, this will prevent single clicks from working on your website with jQuery:

$(window).on('mousemove', function() {
    $('body').attr('rel', Math.random());
});

Edit: For clarification, jQuery's hover event includes mouseenter and mouseleave. These will both prevent click if content is changed.

小猫一只 2024-09-07 00:29:58

更好的解决方案,无需任何 JS、CSS 类和视口检查:您可以使用交互媒体功能 (媒体查询级别 4)

像这样:

@media (hover) {
  // properties
  my:hover {
    color: red;
  }
}

iOS Safari 支持

更多关于:
https://www.jonathanfielding.com/an-introduction-to-交互媒体功能/

A better solution, without any JS, css class and viewport check: you can use Interaction Media Features (Media Queries Level 4)

Like this:

@media (hover) {
  // properties
  my:hover {
    color: red;
  }
}

iOS Safari supports it

More about:
https://www.jonathanfielding.com/an-introduction-to-interaction-media-features/

稀香 2024-09-07 00:29:58

浏览器功能检测库Modernizer包括对触摸事件的检查。

它的默认行为是将类应用于检测到的每个功能的 html 元素。然后,您可以使用这些类来设计文档的样式。

如果未启用触摸事件,Modernizr 可以添加一个 no-touch 类:

<html class="no-touch">

然后使用该类调整您的悬停样式:

.no-touch a:hover { /* hover styles here */ }

您可以 下载自定义 Modernizr 版本以包含您需要的尽可能少或任意数量的功能检测。

以下是一些可能应用的类的示例:

<html class="js no-touch postmessage history multiplebgs
             boxshadow opacity cssanimations csscolumns cssgradients
             csstransforms csstransitions fontface localstorage sessionstorage
             svg inlinesvg no-blobbuilder blob bloburls download formdata">

The browser feature detection library Modernizer includes a check for touch events.

It’s default behavior is to apply classes to your html element for each feature being detected. You can then use these classes to style your document.

If touch events are not enabled Modernizr can add a class of no-touch:

<html class="no-touch">

And then scope your hover styles with this class:

.no-touch a:hover { /* hover styles here */ }

You can download a custom Modernizr build to include as few or as many feature detections as you need.

Here's an example of some classes that may be applied:

<html class="js no-touch postmessage history multiplebgs
             boxshadow opacity cssanimations csscolumns cssgradients
             csstransforms csstransitions fontface localstorage sessionstorage
             svg inlinesvg no-blobbuilder blob bloburls download formdata">
氛圍 2024-09-07 00:29:58

有些设备(正如其他人所说)同时具有触摸和鼠标事件。例如,Microsoft Surface 有一个触摸屏、一个触控板和一个手写笔,当它悬停在屏幕上方时,实际上会引发悬停事件。

任何基于“触摸”事件的存在禁用 :hover 的解决方案也会影响 Surface 用户(以及许多其他类似设备)。许多新的笔记本电脑都是触摸式的,并且会响应触摸事件 - 因此禁用悬停是一种非常糟糕的做法。

这是 Safari 中的一个错误,这种可怕的行为绝对没有理由。我拒绝破坏非 iOS 浏览器,因为 iOS Safari 中的一个错误显然已经存在多年了。我真的希望他们下周在 iOS8 上修复这个问题,但与此同时......

我的解决方案

有些人已经建议使用 Modernizr,那么 Modernizr 允许您创建自己的测试。我在这里基本上所做的是将支持 :hover 的浏览器的想法“抽象”到 Modernizr 测试中,我可以在整个代码中使用该测试,而无需硬编码 if (iOS) 贯穿始终。

 Modernizr.addTest('workinghover', function ()
 {
      // Safari doesn't 'announce' to the world that it behaves badly with :hover
      // so we have to check the userAgent  
      return navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? false : true;
 });

然后CSS变成这样的东西

html.workinghover .rollover:hover 
{
    // rollover css
}

只有在iOS上这个测试才会失败并禁用翻转。

这种抽象最好的部分是,如果我发现它在某个 Android 上出现问题或者它在 iOS9 中得到修复,那么我可以修改测试。

Some devices (as others have said) have both touch and mouse events. The Microsoft Surface for example has a touch screen, a trackpad AND a stylus which actually raises hover events when it is hovered above the screen.

Any solution that disables :hover based on the presence of 'touch' events will also affect Surface users (and many other similar devices). Many new laptops are touch and will respond to touch events - so disabling hovering is a really bad practice.

This is a bug in Safari, there's absolutely no justification for this terrible behavior. I refuse to sabotage non iOS browsers because of a bug in iOS Safari which has apparently been there for years. I really hope they fix this for iOS8 next week but in the meantime....

My solution:

Some have suggested using Modernizr already, well Modernizr allows you to create your own tests. What I'm basically doing here is 'abstracting' the idea of a browser that supports :hover into a Modernizr test that I can use throughout my code without hardcoding if (iOS) throughout.

 Modernizr.addTest('workinghover', function ()
 {
      // Safari doesn't 'announce' to the world that it behaves badly with :hover
      // so we have to check the userAgent  
      return navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? false : true;
 });

Then the css becomes something like this

html.workinghover .rollover:hover 
{
    // rollover css
}

Only on iOS will this test fail and disable rollover.

The best part of such abstraction is that if I find it breaks on a certain android or if it's fixed in iOS9 then I can just modify the test.

新一帅帅 2024-09-07 00:29:58

FastClick 库 添加到您的页面将导致移动设备上的所有点击都将转化为点击事件(无论用户在何处点击) ),因此它还应该解决移动设备上的悬停问题。我编辑了您的小提琴作为示例:http://jsfiddle.net/FvACN/8/

只需在页面上包含 fastclick.min.js 库,并通过以下方式激活:

FastClick.attach(document.body);

作为一个附带好处,它还将消除移动设备所遭受的烦人的 300 毫秒 onClick 延迟。

使用 FastClick 会产生一些小后果,这些后果对您的网站可能很重要,也可能无关紧要:

  1. 如果您点击页面上的某个位置,向上滚动,向下滚动,然后在最初放置的同一位置松开手指,FastClick 会将其解释为“点击”,尽管显然不是。至少在我当前使用的 FastClick 版本(1.0.0)中它是这样工作的。自该版本以来,有人可能已经解决了该问题。
  2. FastClick 消除了某人“双击”的能力。

Adding the FastClick library to your page will cause all taps on a mobile device to be turned into click events (regardless of where the user clicks), so it should also fix the hover issue on mobile devices. I edited your fiddle as an example: http://jsfiddle.net/FvACN/8/.

Just include the fastclick.min.js lib on your page, and activate via:

FastClick.attach(document.body);

As a side benefit, it will also remove the annoying 300ms onClick delay that mobile devices suffer from.

There are a couple of minor consequences to using FastClick that may or may not matter for your site:

  1. If you tap somewhere on the page, scroll up, scroll back down, and then release your finger on the exact same position that you initially placed it, FastClick will interpret that as a "click", even though it's obviously not. At least that's how it works in the version of FastClick that I'm currently using (1.0.0). Someone may have fixed the issue since that version.
  2. FastClick removes the ability for someone to "double click".
吃兔兔 2024-09-07 00:29:58

基本上存在三种情况:

  1. 用户拥有鼠标/指针设备并且可以激活:hover
  2. 用户拥有触摸屏,并且无法激活:hover elements
  3. 用户拥有触摸屏和指针设备两者

如果只有前两种情况可能,即用户拥有其中之一<,则最初接受的答案效果很好/em> 指针或触摸屏。 OP 在 4 年前提出这个问题时,这种情况很常见。一些用户指出,Windows 8 和 Surface 设备使第三种情况更有可能发生。

iOS 对于无法悬停在触摸屏设备上的问题的解决方案(如 @Zenexer 所详述)很聪明,但可能会导致简单的代码行为不当(如 OP 所指出的)。仅对触摸屏设备禁用悬停意味着您仍然需要编写触摸屏友好的替代方案。检测用户何时同时拥有指针和触摸屏会使情况进一步混乱(正如 @Simon_Weaver 所解释的)。

此时,最安全的解决方案是避免使用 :hover 作为用户与您的网站交互的唯一方式。悬停效果是指示链接或按钮可操作的好方法,但不应要求用户悬停元素来在您的网站上执行操作。

重新考虑触摸屏的“悬停”功能 对替代用户体验方法进行了很好的讨论。那里的答案提供的解决方案包括:

  • 用直接操作(始终可见的链接)替换
  • 悬停菜单 用点击菜单替换
  • 悬停菜单 将大量悬停内容移动到单独的页面中

展望未来,这可能是所有新项目的最佳解决方案。接受的答案可能是第二佳解决方案,但请务必考虑也具有指针设备的设备。当设备具有触摸屏时,请注意不要仅仅为了解决 iOS 的 :hover 黑客攻击而消除功能。

There are basically three scenarios:

  1. User only has a mouse/pointer device and can activate :hover
  2. User only has a touchscreen, and can not activate :hover elements
  3. User has both a touchscreen and a pointer device

The originally accepted answer works great if only the first two scenarios are possible, where a user has either pointer or touchscreen. This was common when the OP asked the question 4 years ago. Several users have pointed out that Windows 8 and Surface devices are making the third scenario more likely.

The iOS solution to the problem of not being able to hover on touchscreen devices (as detailed by @Zenexer) is clever, but can cause straightforward code to misbehave (as noted by the OP). Disabling hover only for touchscreen devices means that you will still need to code a touchscreen friendly alternative. Detecting when a user has both pointer and touchscreen further muddies the waters (as explained by @Simon_Weaver).

At this point, the safest solution is to avoid using :hover as the only way a user can interact with your website. Hover effects are a good way of indicating that a link or button is actionable, but a user should not be required to hover an element to perform an action on your website.

Re-thinking “hover” functionality with touchscreens in mind has a good discussion about alternative UX approaches. The solutions provided by the answer there include:

  • Replacing hover menus with direct actions (always visible links)
  • Replacing on-hover menus with on-tap menus
  • Moving large amounts of on-hover content into a separate page

Moving forward, this will probably be the best solution for all new projects. The accepted answer is probably the second best solution, but be sure to account for devices that also have pointer devices. Be careful not to eliminate functionality when a device has a touchscreen just to work around iOS's :hover hack.

∝单色的世界 2024-09-07 00:29:58

JQuery 版本
在你的 .css 使用中
.no-touch .my-element:hover
适用于您所有的悬停规则
包含 JQuery 和以下脚本

function removeHoverState(){
    $("body").removeClass("no-touch");
}

然后在 body 标记中添加
class="no-touch" ontouchstart="removeHoverState()"

一旦 ontouchstart 触发,所有悬停状态的类就会被删除

The JQuery version
in your .css use
.no-touch .my-element:hover
for all your hover rules
include JQuery and the following script

function removeHoverState(){
    $("body").removeClass("no-touch");
}

Then in body tag add
class="no-touch" ontouchstart="removeHoverState()"

as soon as the ontouchstart fires the class for all hover states is removed

七秒鱼° 2024-09-07 00:29:58

我同意禁用悬停触摸是可行的方法。

但是,为了省去重写 css 的麻烦,只需将任何 :hover 项包装在 @supports not (-webkit-overflow-scrolling: touch) {}

.hover, .hover-iOS {
  display:inline-block;
  font-family:arial;
  background:red;
  color:white;
  padding:5px;
}
.hover:hover {
  cursor:pointer;
  background:green;
}

.hover-iOS {
  background:grey;
}

@supports not (-webkit-overflow-scrolling: touch) {
  .hover-iOS:hover {
    cursor:pointer;
    background:blue;
  }

}
<input type="text" class="hover" placeholder="Hover over me" />

<input type="text" class="hover-iOS" placeholder="Hover over me (iOS)" />

I agree disabling hover for touch is the way to go.

However, to save yourself the trouble of re-writing your css, just wrap any :hover items in @supports not (-webkit-overflow-scrolling: touch) {}

.hover, .hover-iOS {
  display:inline-block;
  font-family:arial;
  background:red;
  color:white;
  padding:5px;
}
.hover:hover {
  cursor:pointer;
  background:green;
}

.hover-iOS {
  background:grey;
}

@supports not (-webkit-overflow-scrolling: touch) {
  .hover-iOS:hover {
    cursor:pointer;
    background:blue;
  }

}
<input type="text" class="hover" placeholder="Hover over me" />

<input type="text" class="hover-iOS" placeholder="Hover over me (iOS)" />

枕花眠 2024-09-07 00:29:58

我创建了一个处理触摸事件的系统,而不是仅在触摸不可用时具有悬停效果,这为我解决了问题。首先,我定义了一个用于测试“tap”(相当于“click”)事件的对象。

touchTester = 
{
    touchStarted: false
   ,moveLimit:    5
   ,moveCount:    null
   ,isSupported:  'ontouchend' in document

   ,isTap: function(event)
   {
      if (!this.isSupported) {
         return true;
      }

      switch (event.originalEvent.type) {
         case 'touchstart':
            this.touchStarted = true;
            this.moveCount    = 0;
            return false;
         case 'touchmove':
            this.moveCount++;
            this.touchStarted = (this.moveCount <= this.moveLimit);
            return false;
         case 'touchend':
            var isTap         = this.touchStarted;
            this.touchStarted = false;
            return isTap;
         default:
            return true;
      }
   }
};

然后,在我的事件处理程序中,我执行如下操作:

$('#nav').on('click touchstart touchmove touchend', 'ul > li > a'
            ,function handleClick(event) {
               if (!touchTester.isTap(event)) {
                  return true;
               }

               // touch was click or touch equivalent
               // nromal handling goes here.
            });

Instead of only having hover effects when touch is not available I created a system for handling touch events and that has solved the problem for me. First, I defined an object for testing for "tap" (equivalent to "click") events.

touchTester = 
{
    touchStarted: false
   ,moveLimit:    5
   ,moveCount:    null
   ,isSupported:  'ontouchend' in document

   ,isTap: function(event)
   {
      if (!this.isSupported) {
         return true;
      }

      switch (event.originalEvent.type) {
         case 'touchstart':
            this.touchStarted = true;
            this.moveCount    = 0;
            return false;
         case 'touchmove':
            this.moveCount++;
            this.touchStarted = (this.moveCount <= this.moveLimit);
            return false;
         case 'touchend':
            var isTap         = this.touchStarted;
            this.touchStarted = false;
            return isTap;
         default:
            return true;
      }
   }
};

Then, in my event handler I do something like the following:

$('#nav').on('click touchstart touchmove touchend', 'ul > li > a'
            ,function handleClick(event) {
               if (!touchTester.isTap(event)) {
                  return true;
               }

               // touch was click or touch equivalent
               // nromal handling goes here.
            });
遇见了你 2024-09-07 00:29:58

感谢@Morgan Cheng 的回答,但是我稍微修改了 JS 函数来获取“touchstart”(代码取自@Timothy Perez answer),但是,您需要 jQuery 1.7+

  $(document).on({ 'touchstart' : function(){
      //do whatever you want here
    } });

Thanks @Morgan Cheng for the answer, however I've slightly modified the JS function for getting the "touchstart" (code taken from @Timothy Perez answer), though, you need jQuery 1.7+ for this

  $(document).on({ 'touchstart' : function(){
      //do whatever you want here
    } });
逆流 2024-09-07 00:29:58

鉴于 Zenexer 提供的响应,不需要额外 HTML 标签的模式是:

jQuery('a').on('mouseover', function(event) {
    event.preventDefault();
    // Show and hide your drop down nav or other elem
});
jQuery('a').on('click', function(event) {
    if (jQuery(event.target).children('.dropdown').is(':visible') {
        // Hide your dropdown nav here to unstick
    }
});

此方法首先触发鼠标悬停,然后单击。

Given the response provided by Zenexer, a pattern that requires no additional HTML tags is:

jQuery('a').on('mouseover', function(event) {
    event.preventDefault();
    // Show and hide your drop down nav or other elem
});
jQuery('a').on('click', function(event) {
    if (jQuery(event.target).children('.dropdown').is(':visible') {
        // Hide your dropdown nav here to unstick
    }
});

This method fires off the mouseover first, the click second.

鹿童谣 2024-09-07 00:29:58

对于那些在 iOS Safari 上禁用 :hover 事件的常见用例,最简单的方法是对 :hover 事件使用最小宽度媒体查询,该查询保持在您要避免使用的设备的屏幕宽度。例子:

@media only screen and (min-width: 1024px) {
  .my-div:hover { // will only work on devices larger than iOS touch-enabled devices. Will still work on touch-enabled PCs etc.
    background-color: red;
  }
}

For those with common use case of disabling :hover events on iOS Safari, the simplest way is to use a min-width media query for your :hover events which stays above the screen width of the devices you are avoiding. Example:

@media only screen and (min-width: 1024px) {
  .my-div:hover { // will only work on devices larger than iOS touch-enabled devices. Will still work on touch-enabled PCs etc.
    background-color: red;
  }
}
瀟灑尐姊 2024-09-07 00:29:58

对于仍在寻找解决方案的人,如果上述方法都不起作用,

请尝试一下,

@media (hover: hover)
{
    .Link:hover
    {
        color:#00d8fe;
    }
}

此悬停伪代码仅适用于带有指针的设备,并且在仅具有 .active 类的触摸设备上正常工作。

For someone still looking for a solution if none of the above worked,

Try this,

@media (hover: hover)
{
    .Link:hover
    {
        color:#00d8fe;
    }
}

This hover pseudo will only be applied for devices with pointers and works normal on touch devices with just .active classes.

无声静候 2024-09-07 00:29:58

直接看屏幕大小就可以了....

@media (min-width: 550px) {
    .menu ul li:hover > ul {
    display: block;
}
}

Just look at the screen size....

@media (min-width: 550px) {
    .menu ul li:hover > ul {
    display: block;
}
}
っ〆星空下的拥抱 2024-09-07 00:29:58

这是您想要将其放入的代码

// a function to parse the user agent string; useful for 
// detecting lots of browsers, not just the iPad.
function checkUserAgent(vs) {
    var pattern = new RegExp(vs, 'i');
    return !!pattern.test(navigator.userAgent);
}
if ( checkUserAgent('iPad') ) {
    // iPad specific stuff here
}

heres the code you'll want to place it in

// a function to parse the user agent string; useful for 
// detecting lots of browsers, not just the iPad.
function checkUserAgent(vs) {
    var pattern = new RegExp(vs, 'i');
    return !!pattern.test(navigator.userAgent);
}
if ( checkUserAgent('iPad') ) {
    // iPad specific stuff here
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文