web-dev-qa-db-fra.com

Est-il possible d'ignorer de force la pseudo-classe: hover pour les utilisateurs d'iPhone / iPad?

J'ai quelques menus CSS sur mon site qui se développent avec :hover (Sans js)

Cela fonctionne de manière semi-cassée sur les iDevices, par exemple, un tap activera la règle :hover Et développera le menu, mais si vous tapez ailleurs, le :hover Ne sera pas supprimé. De plus, s'il existe un lien dans l'élément :hover, Vous devez taper deux fois pour l'activer (le premier tap déclenche :hover, Le second tap déclenche le lien).

J'ai pu faire en sorte que les choses fonctionnent bien sur iphone en liant l'événement touchstart.

Le problème est que parfois, safari mobile choisit toujours de déclencher la règle :hover À partir du fichier css au lieu de mes touchstart événements!

Je sais que c'est le problème parce que lorsque je désactive manuellement toutes les règles :hover Dans le CSS, le safari mobile fonctionne très bien (mais les navigateurs classiques ne le sont évidemment plus).

Existe-t-il un moyen d’annuler dynamiquement les règles :hover Pour certains éléments lorsque l’utilisateur utilise le safari mobile?

Consultez et comparez le comportement iOS ici: http://jsfiddle.net/74s35/3/ Remarque: seules certaines propriétés css déclenchent le comportement en deux clics, par exemple. affichage: aucun; mais pas fond: rouge; ou texte-décoration: souligner;

92
Christopher Camps

J'ai trouvé que ": hover" est imprévisible dans Safari pour iPhone/iPad. Parfois, appuyez sur l'élément pour que cet élément ": survolez", 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 avoir toutes les règles CSS avec ": survoler" ci-dessous ".no-touch":

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

Quelque part dans la page, javascript est nécessaire pour supprimer la classe no-touch du corps.

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

Cela ne semble pas parfait, mais cela fonctionne quand même.

76
Morgan Cheng

:hover n'est pas le problème ici. Safari pour iOS suit une règle très étrange. Il déclenche mouseover et mousemove en premier; si quelque chose est modifié au cours de ces événements, le clic et les événements connexes ne sont pas déclenchés:

Diagram of touch event in iOS

mouseenter et mouseleave semblent ê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’arbre DOM. Par exemple, cela empêchera les clics simples de fonctionner sur votre site Web avec jQuery:

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

Edit: pour plus de clarté, l'événement hover de jQuery inclut mouseenter et mouseleave. Ceux-ci empêcheront click si le contenu est modifié.

42
Zenexer

La bibliothèque de détection des fonctionnalités du navigateur Modernizer 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 réglez 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 autant de détections de fonctionnalités que vous le souhaitez.

Voici un exemple de certaines classes pouvant ê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">
18
Beau Smith

Certains appareils (comme d'autres l'ont dit) ont à la fois des événements tactiles et des événements souris. La Microsoft Surface, par exemple, possède un écran tactile, un trackpad ET un stylet qui soulève les événements de survol lorsque celui-ci survole au-dessus de l'écran.

Toute solution qui désactive :hover En fonction de la présence d'événements tactiles affectera également les utilisateurs de Surface (et de nombreux autres périphériques similaires). De nombreux nouveaux ordinateurs portables sont tactiles et réagiront aux événements tactiles. Désactiver le vol stationnaire est donc une très mauvaise pratique.

C'est un bug dans Safari, rien ne peut justifier ce comportement terrible. Je refuse de saboter les navigateurs non iOS à cause d'un bogue dans iOS Safari qui existe apparemment depuis des années. J'espère vraiment qu'ils vont régler ça pour iOS8 la semaine prochaine mais en attendant ...

Ma solution:

Certains ont déjà suggéré d'utiliser Modernizr, et bien Modernizr vous permet de créer vos propres tests. Ce que je fais ici consiste essentiellement à "abstraire" l'idée d'un navigateur qui prend en charge :hover Dans un test Modernizr que je peux utiliser dans tout mon code sans coder en dur if (iOS) pendant tout le processus.

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

Alors le css devient quelque chose comme ça

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

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

La meilleure partie de cette abstraction est que si je trouve qu'il se casse sur un certain Android ou s'il est corrigé dans iOS9, je peux simplement modifier le test.

17
Simon_Weaver

L'ajout de bibliothèque FastClick à votre page entraînera la transformation de tous les taps d'un appareil mobile en événements de clic (quel que soit l'endroit où l'utilisateur clique), ce qui devrait également résoudre le problème du 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-la via:

FastClick.attach(document.body);

En outre, cela éliminera également le retard onClick de 300 ms qui gêne les appareils mobiles.


L'utilisation de FastClick a quelques conséquences mineures qui peuvent ou non avoir de l'importance 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 le doigt sur la position exacte où vous l'avez placée, FastClick interprétera cela comme un "clic", même si ce n'est manifestement pas le cas. Du moins, c'est comme cela que fonctionne 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".
16
Troy

Une meilleure solution, sans vérification de JS, de classe css ni de fenêtre d'affichage: vous pouvez utiliser Interaction Media Features (Requêtes sur les médias de niveau 4)

Comme ça:

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

iOS Safari le supporte

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

13
Estevão Lucas

Il existe essentiellement trois scénarios:

  1. L'utilisateur seulement a un périphérique souris/pointeur et peut activer :hover
  2. L'utilisateur uniquement a un écran tactile et ne peut activer les éléments :hover
  3. L'utilisateur a à la fois un écran tactile et un dispositif de pointeur

La réponse initialement acceptée fonctionne bien si seuls les deux premiers scénarios sont possibles, où un utilisateur a un pointeur ou un écran tactile. C'était courant lorsque le PO a posé la question il y a 4 ans. Plusieurs utilisateurs ont fait remarquer que les périphériques Windows 8 et Surface rendaient le troisième scénario plus probable.

La solution iOS pour résoudre le problème de l'impossibilité de survoler les appareils à écran tactile (comme détaillé par @Zenexer) est astucieuse, 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 aurez toujours besoin de coder une alternative conviviale pour écran tactile. Détecter lorsqu'un utilisateur a à la fois un pointeur et un écran tactile perturbe encore plus les choses (comme l'explique @Simon_Weaver).

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

Repenser la fonctionnalité de "survol" avec les écrans tactiles à l'esprit discute bien des approches alternatives de l'expérience utilisateur. Les solutions apportées par la réponse incluent:

  • Remplacement des menus de survol par des actions directes (liens toujours visibles)
  • Remplacement des menus en vol stationnaire par des menus en mode tap
  • Déplacement de grandes quantités de contenu en survol sur 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 prendre en compte les périphériques qui possèdent également des périphériques de pointeur. Veillez à ne pas éliminer les fonctionnalités lorsqu'un périphérique dispose d'un écran tactile uniquement pour contourner le hack :hover D'iOS.

4
thirdender

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

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

Ensuite, 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é

1
Greg

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

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

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

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 en premier, le clic en second.

0
veryphatic

Je conviens que désactiver le survol tactile est la solution.

Cependant, pour vous épargner la peine de ré-écrire votre css, enveloppez simplement les éléments :hover 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)" />
0
Jordan Young

Au lieu d'avoir uniquement des effets de survol lorsque le toucher n'est pas disponible, j'ai créé un système de gestion des événements tactiles, ce qui a résolu le problème pour moi. Tout d'abord, j'ai défini un objet pour tester les événements "tap" (équivalent à "clic").

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 ceci:

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