Est-il possible de forcer ignorer la pseudoclasse: hover pour les utilisateurs d'iPhone / iPad?

94

J'ai des menus css sur mon site qui se développent avec :hover(sans js)

Cela fonctionne de manière semi-interrompue sur les iDevices, par exemple, un robinet activera la :hoverrègle et développera le menu, mais en tapant ailleurs ne supprimera pas le :hover. De plus, s'il y a un lien à l'intérieur de l'élément qui est :hover'ed, vous devez appuyer deux fois pour activer le lien (premier déclencheur tap :hover, deuxième lien déclencheur tap).

J'ai pu faire fonctionner les choses bien sur iPhone en liant l' touchstartévénement.

Le problème est que parfois le safari mobile choisit toujours de déclencher la :hoverrègle depuis le css au lieu de mes touchstartévénements!

Je sais que c'est le problème car lorsque je désactive tous les :hover manuellement règles dans le css, le safari mobile fonctionne très bien (mais les navigateurs classiques ne le font évidemment plus).

Existe-t-il un moyen «d'annuler» dynamiquement les :hoverrègles pour certains éléments lorsque l'utilisateur est en safari mobile?

Voir et comparer le comportement iOS ici: http://jsfiddle.net/74s35/3/ Remarque: que seules certaines propriétés css déclenchent le comportement en deux clics, par exemple display: none; mais pas de fond: rouge; ou texte-décoration: souligné;

Christopher Camps
la source
7
VEUILLEZ NE PAS désactiver le survol simplement à cause de la présence d'événements «tactiles». Cela affectera négativement les utilisateurs d'ordinateurs portables équipés de périphériques d'entrée tactiles et souris. Voir ma réponse pour plus de détails
Simon_Weaver

Réponses:

77

J'ai trouvé que ": hover" est imprévisible dans Safari iPhone / iPad. Parfois, appuyez sur l'élément pour faire de cet élément ": hover", alors que parfois il dérive vers d'autres éléments.

Pour le moment, j'ai juste un cours «sans contact» au corps.

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

Et ayez toutes les règles CSS avec ": hover" ci-dessous ".no-touch":

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

Quelque part dans la page, j'ai javascript pour supprimer la classe sans contact du corps.

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

Cela n'a pas l'air parfait, mais cela fonctionne quand même.

Morgan Cheng
la source
5
C'est la seule façon de le faire. Ce qui me dérange, c'est que cela pollue le site "normal" avec des trucs qui n'y appartiennent pas et qui ne sont pas pertinents. Tant pis. Merci.
Christopher Camps
Vous pouvez également utiliser des requêtes multimédias avec une solution de secours pour IE
Lime
2
@Shackrock: Je ne pense pas que l'approche "un robinet pour survoler et deux pour cliquer" soit une bonne solution. Puisqu'il n'y a en fait aucun survol réel sur un appareil tactile (jusqu'à ce qu'ils viennent avec des écrans qui sentent votre doigt planer dessus), je pense qu'il est préférable de ne pas simuler du tout le survol. Pour les effets, comme ceux courants sur les boutons et les liens, ignorez simplement l'effet. Pour les menus qui se développent en survolant, faites-les développer en appuyant / en cliquant à la place. Mon 2c.
Adrian Schmidt
18
J'ai récemment trouvé des problèmes dans ce type d'approche en raison des nouveaux systèmes Windows 8 avec entrée tactile et souris
Tom
4
+1 au commentaire de Tom - la vérification de ontouchstart retournera vrai sur les ordinateurs portables tactiles, même lorsqu'ils sont branchés sur un moniteur externe (où un site convivial avec des états de survol est plus souhaitable).
gregdev
45

:hovern'est pas le problème ici. Safari pour iOS suit une règle très étrange. Il tire mouseoveret d' mousemoveabord; si quelque chose est changé pendant ces événements, le «clic» et les événements associés ne sont pas déclenchés:

Diagramme d'événement tactile sous iOS

mouseenteret mouseleavesemblent être inclus, bien qu'ils ne soient pas spécifiés dans le graphique.

Si vous modifiez quoi que ce soit à la suite de ces événements, les événements de clic ne seront pas déclenchés. Cela inclut quelque chose de plus haut dans l'arborescence DOM. Par exemple, cela empêchera les simples clics de fonctionner sur votre site Web avec jQuery:

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

Edit: Pour clarification, l' hoverévénement de jQuery comprendmouseenter et mouseleave. Ceux-ci empêcheront tous les deux clicksi le contenu est modifié.

Zenexer
la source
5
bien sûr: le hover est le problème :-) ou plutôt l'implémentation buggy de Safari. c'est un contexte intéressant sur la question, mais Apple est la seule partie qui peut corriger ce terrible comportement. il a soit besoin de «démasquer» quand on clique sur quelque chose en dehors des éléments, soit de ne jamais «survoler» en premier lieu. le comportement actuel rompt fondamentalement tout site Web qui utilise: survolez pour une souris
Simon_Weaver
cela m'a aidé à réaliser que le hovergestionnaire jquery empêchait un clickgestionnaire séparé d' être touché - quelle blague!
Simon_Weaver le
1
Réponse brillante et meilleure explication du problème que j'ai rencontré. Ce ne sont pas seulement les survols CSS qui causent le problème du "double clic", mais littéralement toute modification du DOM après les événements mouseover / mouseenter et al.
Oliver Joseph Ash
Voici un exemple qui montre que l'événement de clic ne se déclenche pas lorsque le DOM est modifié sur mouseenter jsbin.com/maseti/5/edit?html,js,output
Oliver Joseph Ash
@Oliver Joseph Ash Bien, d'où l'exemple JS dans la réponse. ;)
Zenexer
18

Modernizer de la bibliothèque de détection des fonctionnalités du navigateur inclut une vérification des événements tactiles.

Son comportement par défaut consiste à appliquer des classes à votre élément html pour chaque fonctionnalité détectée. Vous pouvez ensuite utiliser ces classes pour styliser votre document.

Si les événements tactiles ne sont pas activés, Modernizr peut ajouter une classe de no-touch:

<html class="no-touch">

Et puis définissez vos styles de survol avec cette classe:

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

Vous pouvez télécharger une version personnalisée de Modernizr pour inclure aussi peu ou autant de détections de fonctionnalités que vous en avez besoin.

Voici un exemple de certaines classes qui peuvent être appliquées:

<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">
Beau Smith
la source
Sachez que sur les versions récentes de Chrome sur les ordinateurs de bureau, Modernizr signale «toucher». Ceci est apparemment corrigé dans la version 3 avec touchevents.
Craig Jacobs
18

Certains appareils (comme d'autres l'ont dit) ont à la fois des événements tactiles et souris. Le Microsoft Surface, par exemple, dispose d'un écran tactile, d'un trackpad ET d'un stylet qui déclenche en fait des événements de survol lorsqu'il est au-dessus de l'écran.

Toute solution qui se désactive en :hoverfonction de la présence d'événements «tactiles» affectera également les utilisateurs de Surface (et de nombreux autres appareils similaires). De nombreux nouveaux ordinateurs portables sont tactiles et répondront aux événements tactiles - la désactivation du survol est donc une très mauvaise pratique.

C'est un bogue dans Safari, il n'y a absolument aucune justification à ce comportement terrible. Je refuse de saboter les navigateurs non iOS à cause d'un bug dans iOS Safari qui est apparemment là depuis des années. J'espère vraiment qu'ils corrigent ce problème pour iOS8 la semaine prochaine, mais en attendant ....

Ma solution :

Certains ont déjà suggéré d'utiliser Modernizr, enfin Modernizr vous permet de créer vos propres tests. Ce que je fais essentiellement ici, c'est «faire abstraction» de l'idée d'un navigateur qui prend :hoveren charge un test Modernizr que je peux utiliser dans tout mon code sans codage en dur 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;
 });

Ensuite, le css devient quelque chose comme ça

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

Ce test échouera et désactivera le rollover uniquement sur iOS.

La meilleure partie d'une telle abstraction est que si je trouve que cela casse sur un certain Android ou s'il est corrigé dans iOS9, je peux simplement modifier le test.

Simon_Weaver
la source
Cette solution est à la fois méchante et la meilleure qui soit. Cela ne casse que si un navigateur qui a du mal avec le survol a à la fois le toucher et le clic, mais ce n'est pas le cas pour les navigateurs tactiles uniquement sur iPad / iPhone. Ma suggestion est de l'implémenter uniquement en tant que correctif d'optimisation (pour supprimer le scintillement du survol lorsque vous utilisez déjà fastclick.js) et de vous assurer que tout fonctionne également sans cela.
Gersom
Merci. Je suis d'accord. Curieux de voir ce que fait iOS 9 pour ce problème - le cas échéant.
Simon_Weaver
Je suis allé dans la direction opposée:html:not(.no-hover) .category:hover { ...
Chris Marisic
+1 pour "Safari n'annonce pas au monde qu'il se comporte mal avec: hover" Ce problème rend iOS bien pire que n'importe quelle version d'IE à mon avis ...
Spencer O'Reilly
16

L'ajout de la bibliothèque FastClick à votre page entraînera la transformation de tous les taps sur un appareil mobile en événements de clic (quel que soit l'endroit où l'utilisateur clique), il devrait donc également résoudre le problème de survol sur les appareils mobiles. J'ai édité votre violon à titre d'exemple: http://jsfiddle.net/FvACN/8/ .

Incluez simplement la bibliothèque fastclick.min.js sur votre page et activez via:

FastClick.attach(document.body);

En plus, cela supprimera également le retard ennuyeux de 300 ms onClick dont souffrent les appareils mobiles.


Il y a quelques conséquences mineures à l'utilisation de FastClick qui peuvent ou peuvent ne pas être importantes pour votre site:

  1. Si vous appuyez quelque part sur la page, faites défiler vers le haut, faites défiler vers le bas, puis relâchez votre doigt exactement à la même position que vous l'avez initialement placé, FastClick interprétera cela comme un «clic», même si ce n'est évidemment pas le cas. Au moins, c'est ainsi que cela fonctionne dans la version de FastClick que j'utilise actuellement (1.0.0). Quelqu'un a peut-être résolu le problème depuis cette version.
  2. FastClick supprime la possibilité pour quelqu'un de "double-cliquer".
Troie
la source
1
Seriez-vous prêt à partager un autre exemple concret de la façon dont je peux faire cela pour mon site Web. Je ne sais rien sur javascript, donc je ne sais pas trop comment cela fonctionnerait. J'ai tous ces liens sur mon site Web sur lesquels les utilisateurs survolent généralement avant de cliquer, mais pour ipad / mobile, je veux faire en sorte qu'ils n'aient pas à cliquer deux fois.
Andy
@Andy - On ne sait pas exactement ce dont vous avez besoin et ce qui vous manque ... Qu'avez-vous essayé, jusqu'à présent, et quelles erreurs voyez-vous, le cas échéant? Il serait peut-être préférable de créer une nouvelle question Stackoverflow, si vous ne l'avez pas déjà fait, avec un exemple de ce que vous essayez de faire (?) Ou essayez de clarifier ce que c'est dans l'exemple ci-dessus, jsfiddle que vous ne faites pas ' t comprendre.
Troy
cela peut-il être utilisé dans les applications d'une seule page? Je veux dire quand le contenu DOM est mis à jour à chaque fois
Sebastien Lorber
Oui, je l'utilise avec des applications d'une seule page.
Troy
16

Une meilleure solution, sans JS, classe css et vérification de la fenêtre: vous pouvez utiliser les fonctionnalités d'Interaction Media (Media Queries Level 4)

Comme ça:

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

iOS Safari le prend en charge

En savoir plus: https://www.jonathanfielding.com/an-introduction-to-interaction-media-features/

Estevão Lucas
la source
Non pris en charge par Firefox cependant (au moment de ce commentaire bien sûr)
Frank
Ceci est désormais également pris en charge par Firefox (FF 64+, publié en décembre 2018).
wunch
4

Il existe essentiellement trois scénarios:

  1. L' utilisateur ne dispose d' un dispositif de souris / pointeur et peut activer:hover
  2. L' utilisateur ne dispose d' un écran tactile, et ne peut pas activer des :hoveréléments
  3. L'utilisateur dispose à la fois d' un écran tactile et d'un pointeur

La réponse acceptée à l'origine fonctionne très bien si seuls les deux premiers scénarios sont possibles, où un utilisateur dispose d' un pointeur ou d'un écran tactile. C'était courant lorsque le PO a posé la question il y a 4 ans. Plusieurs utilisateurs ont souligné que les appareils Windows 8 et Surface rendent le troisième scénario plus probable.

La solution iOS au problème de ne pas pouvoir survoler les appareils à écran tactile (comme détaillé par @Zenexer) est intelligente, mais peut entraîner un mauvais comportement du code simple (comme indiqué par l'OP). La désactivation du survol uniquement pour les appareils à écran tactile signifie que vous devrez toujours coder une alternative conviviale pour l'écran tactile. Détecter quand un utilisateur a à la fois le pointeur et l'écran tactile brouille encore plus les eaux (comme expliqué par @Simon_Weaver).

À ce stade, la solution la plus sûre consiste à éviter d'utiliser :hovercomme seul moyen pour un utilisateur d'interagir avec votre site Web. Les effets de survol sont un bon moyen d'indiquer qu'un lien ou un bouton peut être actionné, mais un utilisateur ne devrait pas être obligé de survoler un élément pour effectuer une action sur votre site Web.

Repenser la fonctionnalité de «survol» avec les écrans tactiles à l'esprit a une bonne discussion sur les approches UX alternatives. Les solutions apportées par la réponse comprennent:

  • Remplacement des menus de survol par des actions directes (liens toujours visibles)
  • Remplacement des menus au survol par des menus au toucher
  • Déplacement de grandes quantités de contenu en survol vers une page distincte

À l'avenir, ce sera probablement la meilleure solution pour tous les nouveaux projets. La réponse acceptée est probablement la deuxième meilleure solution, mais assurez-vous de tenir compte des appareils qui ont également des dispositifs de pointeur. Veillez à ne pas éliminer les fonctionnalités lorsqu'un appareil dispose d'un écran tactile juste pour contourner le :hoverpiratage d'iOS .

tiers
la source
1

La version JQuery dans votre .css utilise .no-touch .my-element: hover pour toutes vos règles de survol incluent JQuery et le script suivant

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

Puis dans la balise body, ajoutez class = "no-touch" ontouchstart = "removeHoverState ()"

dès que l'ontouchstart déclenche la classe pour tous les états de survol est supprimée

Greg
la source
0

Au lieu de n'avoir que des effets de survol lorsque le toucher n'est pas disponible, j'ai créé un système pour gérer les événements tactiles et cela a résolu le problème pour moi. Tout d'abord, j'ai défini un objet pour tester les événements "tap" (équivalent à "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;
      }
   }
};

Ensuite, dans mon gestionnaire d'événements, je fais quelque chose comme ce qui suit:

$('#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.
            });
Donut
la source
0

Merci @Morgan Cheng pour la réponse, mais j'ai légèrement modifié la fonction JS pour obtenir le " touchstart " (code tiré de la réponse @Timothy Perez ), cependant, vous avez besoin de jQuery 1.7+ pour cela

  $(document).on({ 'touchstart' : function(){
      //do whatever you want here
    } });
3Gee
la source
0

Compte tenu de la réponse fournie par Zenexer, un modèle qui ne nécessite aucune balise HTML supplémentaire est:

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

Cette méthode déclenche le survol de la souris en premier, le clic en second.

veryphatique
la source
0

Je suis d'accord que la désactivation du survol pour le toucher est la voie à suivre.

Cependant, pour vous éviter d'avoir à réécrire votre css, enveloppez simplement tous les :hoveréléments dans @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)" />

Jordan Young
la source
0

Pour ceux qui ont des cas d'utilisation courants de désactivation d' :hoverévénements sur iOS Safari, le moyen le plus simple consiste à utiliser une requête multimédia de largeur minimale pour vos :hoverévénements qui reste au-dessus de la largeur d'écran des appareils que vous évitez. Exemple:

@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;
  }
}
Brian Scramlin
la source
-6

Regardez la taille de l'écran ...

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

voici le code dans lequel vous voudrez le placer

// 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
}
Luke
la source