Problème de survol de l’iPad / iPhone oblige l’utilisateur à double-cliquer sur un lien

J’ai quelques sites Web que j’ai construits il y a des temps, qui utilisent des événements de souris jquery … Je viens de recevoir un ipad et j’ai remarqué que tous les événements de souris sont traduits en clics … .. (le premier vol stationnaire, que le clic réel)

existe-t-il une solution de rechange prête à résoudre ce problème? peut-être une commande jquery que je devrais avoir utilisé au lieu de mouseover / out etc .. merci!

Je n’ai pas testé cela complètement, mais comme iOS déclenche des événements tactiles, cela pourrait fonctionner, en supposant que vous soyez dans un paramètre jQuery.

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

L’idée est que Mobile WebKit déclenche un événement de fin de touche à la fin d’un tap pour que nous touchend cela, puis redirige le navigateur dès qu’un événement de touchend a été déclenché sur un lien.

Votre question n’est pas tout à fait claire, mais si vous voulez simplement éliminer le double clic, tout en conservant l’effet de survol pour la souris, je vous conseille de:

  • Ajoutez des effets de touchstart sur touchstart et mouseenter .
  • Supprimez les effets de mouseleave sur la mouseleave , mouseleave et click .

Contexte

Pour simuler une souris, les navigateurs tels que Webkit Mobile déclenchent les événements suivants si l’utilisateur touche et libère un doigt sur l’écran tactile (comme l’iPad) (source: Touch And Mouse sur html5rocks.com):

  1. touchstart
  2. touchmove
  3. touchend
  4. 300ms de retard, où le navigateur s’assure qu’il s’agit d’un simple tapotement, pas d’un double tapotement
  5. mouseover
  6. mouseenter
    • Remarque : Si un événement mouseover , mouseenter ou mousemove modifie le contenu de la page, les événements suivants ne sont jamais déclenchés.
  7. mousemove
  8. mousedown
  9. mouseup
  10. click

Il ne semble pas possible de dire simplement au navigateur Web de sauter les événements de la souris.

Pire encore, si un événement mouseover modifie le contenu de la page, l’événement click n’est jamais déclenché, comme expliqué dans Safari Web Content Guide – Gestion des événements , en particulier la figure 6.4 des événements One-Finger . Ce qu’est exactement un “changement de contenu” dépendra du navigateur et de la version. J’ai trouvé que pour iOS 7.0, un changement de couleur d’arrière-plan n’est pas (ou plus?) Un changement de contenu.

Solution expliquée

Récapituler:

  • Ajoutez des effets de touchstart sur touchstart et mouseenter .
  • Supprimez les effets de mouseleave sur la mouseleave , mouseleave et click .

Notez qu’il n’y a pas d’action sur le touchend !

Cela fonctionne clairement pour les événements de souris: les mouseenter et mouseleave (versions légèrement améliorées de mouseover et mouseout ) sont mouseleave et ajoutent et suppriment le survol.

Si l’utilisateur click lien sa, l’effet de survol est également supprimé. Cela garantit qu’il est supprimé si l’utilisateur appuie sur le bouton Retour dans le navigateur Web.

Cela fonctionne également pour les événements tactiles: au touchstart l’effet de touchstart est ajouté. Il n’est pas retiré sur le touchend . Il est ajouté à nouveau sur mouseenter , et comme cela ne provoque aucune modification du contenu (il a déjà été ajouté), l’événement click est également déclenché et le lien est suivi sans que l’utilisateur ait à cliquer à nouveau!

Le délai de 300 ms qu’un navigateur a entre un événement de touchstart et un click est effectivement utilisé, car l’effet de survol sera affiché pendant cette courte période.

Si l’utilisateur décide d’annuler le clic, un mouvement du doigt le fera tout aussi normalement. Normalement, cela pose problème car aucun événement mouseleave n’est déclenché et l’effet de mouseleave rest en place. Heureusement, cela peut facilement être résolu en supprimant l’effet de touchmove sur touchmove .

C’est tout!

Notez qu’il est possible de supprimer le délai de 300 ms, par exemple en utilisant la bibliothèque FastClick , mais cette question est hors de scope.

Solutions alternatives

J’ai trouvé les problèmes suivants avec les alternatives suivantes:

  • Détection de navigateur: extrêmement vulnérable aux erreurs. Suppose qu’un appareil soit doté d’une souris ou d’une touche tactile, tandis qu’une combinaison des deux deviendra de plus en plus courante lorsque les écrans tactiles proliféreront.
  • Détection des médias CSS: la seule solution de type CSS que je connaisse. Toujours sujettes à des erreurs, et suppose toujours qu’un appareil possède une souris ou une touche, alors que les deux sont possibles.
  • Émuler l’événement click dans touchend : Cela suivra incorrectement le lien, même si l’utilisateur voulait seulement faire défiler ou zoomer, sans avoir l’intention de cliquer sur le lien.
  • Utilisez une variable pour supprimer les événements de la souris: cette touchend définit une variable en touchend qui est utilisée comme condition if dans les événements de souris suivants pour empêcher les modifications d’état à ce moment-là. La variable est réinitialisée dans l’événement click. Ceci est une solution décente si vous ne voulez vraiment pas un effet de survol sur les interfaces tactiles. Malheureusement, cela ne fonctionne pas si une touchend est déclenchée pour une autre raison et qu’aucun événement de clic n’est déclenché (par exemple, l’utilisateur fait défiler ou zoomer) et essaie ensuite de suivre le lien avec une souris interface).

Lectures complémentaires

Voir également le problème de double clic sur iPad / iPhone et Désactiver les effets de survol sur les navigateurs mobiles .

Il semble y avoir une solution CSS après tout. La raison pour laquelle Safari attend une seconde pression est due à l’image (ou aux éléments) d’arrière-plan que vous atsortingbuez généralement à l’événement: hover. S’il n’y en a pas, vous n’aurez aucun problème. La solution consiste à cibler la plate-forme iOS avec un fichier CSS secondaire (ou un style en cas d’approche JS) qui remplace: survolez le fond pour hériter par exemple et gardez caché les éléments que vous alliez afficher sur la souris:

Voici un exemple de CSS et HTML – un bloc de produit avec une étiquette étoilée sur la souris:

HTML:

 Some text here 

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 (CSS secondaire):

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

Ce qui a fonctionné pour moi, c’est ce que d’autres ont déjà dit:

Ne pas afficher / masquer les éléments en survol ou mousemove (ce qui est l’événement dans mon cas).

Voici ce que dit Apple ( https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html ):

Un élément cliquable est un lien, un élément de formulaire, une zone de carte-image ou tout autre élément comportant des gestionnaires mousemove, mousedown, mouseup ou onclick.

Si l’utilisateur appuie sur un élément cliquable, les événements arrivent dans cet ordre: survol, mousemove, mousedown, mouseup et cliquez. En outre, si le contenu de la page change lors de l’événement mousemove, aucun événement ultérieur de la séquence n’est envoyé. Ce comportement permet à l’utilisateur d’appuyer sur le nouveau contenu.

Vous pouvez donc utiliser la solution de @ woop: détecter l’agent utilisateur, vérifier s’il s’agit d’un périphérique iOS et lier l’événement. J’ai fini par utiliser cette technique car elle convient à mes besoins et il est plus logique de ne pas lier les événements de vol stationnaire lorsque vous ne le voulez pas.

Mais … si vous ne voulez pas gâcher les userAgents et toujours masquer / afficher les éléments sur hover / mousemove, j’ai découvert que vous pouviez le faire en utilisant du javascript natif, comme ceci:

 $("body").on("mouseover", function() { document.getElementsByTagName("my-class")[0].style.display = 'block'; //show element document.querySelector(".my-selector div").style.display = 'none'; // hide element }); 

Cela fonctionnera sur la version de bureau et ne fera rien sur la version mobile.

Et pour un peu plus de compatibilité …

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

La solution de cduruk était assez efficace, mais a causé des problèmes sur quelques parties de mon site. Comme j’utilisais déjà jQuery pour append la classe de hover CSS, la solution la plus simple consistait simplement à ne pas append la classe de hover CSS sur les appareils mobiles (ou plus précisément à l’append SEULEMENT lorsqu’il n’était PAS sur un appareil mobile).

Voici l’idée générale:

 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 réduit à titre indicatif

Je pense qu’il serait sage d’essayer mouseenter à la place de mouseover . C’est ce qui est utilisé en interne lors de la liaison avec .hover(fn,fn) et est généralement ce que vous voulez.

J’ai eu les problèmes suivants avec les solutions existantes et trouvé quelque chose qui semble les résoudre tous. Cela suppose que vous visez quelque chose avec plusieurs navigateurs, que vous traversiez un périphérique et que vous ne souhaitiez pas renifler les périphériques.

Les problèmes que cela résout

En utilisant simplement touchstart ou touchend :

  • L’événement se déclenche lorsque des personnes essaient de faire défiler le contenu et se trouve avoir eu le doigt sur cet élément lorsqu’il a commencé à balayer – ce qui a déclenché l’action de manière inattendue.
  • Peut déclencher l’événement sur une pression longue , similaire à un clic droit sur le bureau. Par exemple, si votre événement de clic passe à l’URL X et que l’utilisateur appuie sur pour ouvrir X dans un nouvel onglet, l’utilisateur sera confus de voir X ouvert dans les deux tabs. Sur certains navigateurs (iPhone, par exemple), cela peut même empêcher l’affichage du menu de presse longue.

Déclencher des événements mouseover sur touchstart et mouseout sur touchmove a des conséquences moins graves, mais interfère avec le comportement habituel du navigateur, par exemple:

  • Un appui long déclencherait un survol de la souris qui ne se termine jamais.
  • De nombreux navigateurs Android traitent l’emplacement du doigt sur l’ touchstart comme un touchstart la mouseover , qui est mouseout sur l’ touchstart suivant. Une façon de voir le contenu de la souris dans Android consiste donc à toucher la zone d’intérêt et à bouger votre doigt, en faisant défiler légèrement la page. Traiter la touchmove comme la mouseout casse cela.

La solution

En théorie, vous pouvez simplement append un drapeau avec touchmove , mais les iPhones déclenchent le toucher même s’il n’y a pas de mouvement. En théorie, il vous suffit de comparer la touchstart et la touchend événements pageX et pageY mais sur les iPhones, il n’y a pas de pageX ou de pageY .

Donc malheureusement, pour couvrir toutes les bases, cela finit par se compliquer un peu.

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

En théorie, il existe des moyens d'obtenir l'heure exacte utilisée pour un longpress au lieu d'utiliser simplement 1000 comme approximation, mais en pratique, ce n'est pas si simple et il est préférable d'utiliser un proxy raisonnable .

La réponse de MacFreak m’a été extrêmement utile. Voici un code pratique au cas où cela vous aiderait.

PROBLÈME – appliquer la touche signifie chaque fois que vous faites défiler votre doigt sur un élément, il répond comme si vous le pressiez, même si vous essayiez simplement de défiler.

Je crée un effet avec jQuery qui fait apparaître une ligne sous certains boutons pour “mettre en évidence” le bouton survolé. Je ne veux pas que cela signifie que vous devez appuyer sur le bouton deux fois sur les appareils tactiles pour suivre le lien.

Voici les boutons:

    

Je veux que le div “menu_underline” apparaisse en fondu au passage de la souris et disparaisse à la souris. MAIS je veux que les appareils tactiles puissent suivre le lien en un seul clic, pas deux.

SOLUTION – Voici le jQuery pour le faire fonctionner:

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

Merci beaucoup pour votre aide sur ce MacFreak.

Je viens de découvrir que cela fonctionne si vous ajoutez un écouteur vide, ne me demandez pas pourquoi, mais je l’ai testé sur iPhone et iPad avec iOS 9.3.2 et cela a bien fonctionné.

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

Pas besoin de faire compliqué.

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

Je “pense” que vos liens n’ont pas d’événement onmouseover, où 1 touche active onmouseover et le double tap active le lien. mais idk. Je n’ai pas d’iPad. Je pense que tu dois utiliser des événements gestuels / tactiles.

https://developer.apple.com/documentation/webkitjs

J’ai eu le même problème mais pas sur un appareil tactile. L’événement se déclenche chaque fois que vous cliquez. Il y a quelque chose à propos de la mise en queue des événements.

Cependant, ma solution était la suivante: sur événement de clic (ou toucher?) Vous définissez une timer. Si vous cliquez à nouveau sur le lien dans X ms, vous retournez simplement false.

Pour définir par élément timer, vous pouvez utiliser $.data() .

Cela peut également résoudre le problème @Ferdy décrit ci-dessus.

Si vous utilisez Modernizr, il est très facile d’utiliser Modernizr.touch comme mentionné précédemment.

Cependant, je préfère utiliser une combinaison de Modernizr.touch et de test d’agent d’utilisateur, juste pour être sûr.

 var deviceAgent = navigator.userAgent.toLowerCase(); var isTouchDevice = Modernizr.touch || (deviceAgent.match(/(iphone|ipod|ipad)/) || deviceAgent.match(/(android)/) || deviceAgent.match(/(iemobile)/) || deviceAgent.match(/iphone/i) || deviceAgent.match(/ipad/i) || deviceAgent.match(/ipod/i) || deviceAgent.match(/blackberry/i) || deviceAgent.match(/bada/i)); function Tipsy(element, options) { this.$element = $(element); this.options = options; this.enabled = !isTouchDevice; this.fixTitle(); }; 

Si vous n’utilisez pas Modernizr, vous pouvez simplement remplacer la fonction Modernizr.touch ci-dessus par ('ontouchstart' in document.documentElement)

Notez également que le test de l’agent utilisateur iemobile vous donnera une plus large gamme de périphériques mobiles Microsoft détectés que Windows Phone.

Vous pouvez utiliser click touchend ,

Exemple:

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

L’exemple ci-dessus affectera tous les liens sur les appareils tactiles.

Si vous souhaitez cibler uniquement des liens spécifiques, vous pouvez le faire en définissant une classe sur eux, à savoir:

HTML:

Prevent extra click on touch device

Jquery:

 $('a.prevent-extra-click').on('click touchend', function() { var linkToAffect = $(this); var linkToAffectHref = linkToAffect.attr('href'); window.location = linkToAffectHref; }); 

À votre santé,

Jeroen

J’ai rencontré une situation similaire où des événements étaient liés aux états mouseenter / mouseleave / click d’un élément, mais sur un iPhone, l’utilisateur devait double-cliquer sur l’élément pour déclencher l’événement mouseenter, puis à nouveau pour déclencher l’événement click .

J’ai résolu ce problème en utilisant une méthode similaire à celle décrite ci-dessus, mais j’ai utilisé le plug-in jQuery $ .browser (pour jQuery 1.9>) et ajouté un événement .sortinggger à l’événement de liaison mouseenter, comme suit:

 // mouseenter event $('.element').on( "mouseenter", function() { // insert mouseenter events below // double click fix for iOS and mouseenter events if ($.browser.iphone || $.browser.ipad) $(this).sortinggger('click'); }); // mouseleave event $('.element').on( "mouseleave", function() { // insert mouseout events below }); // onclick event $('.element').on( "click", function() { // insert click events below }); 

Le .sortinggger évite d’avoir à double-cliquer sur l’élément en lançant le gestionnaire d’événement .click sur mouseenter (ou clic initial) de l’élément lorsqu’il est affiché sur un iPhone ou un iPad. Ce n’est peut-être pas la solution la plus élégante, mais elle fonctionne très bien dans mon cas et utilise un plug-in que j’avais déjà en place, et m’a obligé à append une seule ligne de code pour faire fonctionner mes événements existants sous ces périphériques.

Vous pouvez obtenir le plug-in jQuery $ .browser ici: https://github.com/gabceb/jquery-browser-plugin

Juste une amélioration pour éviter la redirection lorsque vous faites glisser votre doigt sur un lien par erreur.

 // tablet "one touch (click)" X "hover" > link redirection $('a').on('touchmove touchend', function(e) { // if touchmove>touchend, set the data() for this element to true. then leave touchmove & let touchend fail(data=true) redirection if (e.type == 'touchmove') { $.data(this, "touchmove_cancel_redirection", true ); return; } // if it's a simple touchend, data() for this element doesn't exist. if (e.type == 'touchend' && !$.data(this, "touchmove_cancel_redirection")) { var el = $(this); var link = el.attr('href'); window.location = link; } // if touchmove>touchend, to be redirected on a future simple touchend for this element $.data(this, "touchmove_cancel_redirection", false ); }); 

Avec l’inspiration de MacFreak, j’ai assemblé quelque chose qui fonctionne pour moi.

Cette méthode js empêche le survol de coller sur un ipad, et empêche le clic d’enregistrer en deux clics dans certains cas. En CSS, si vous en avez: hover psudo classes dans votre css, changez-les en .hover Par exemple .some-class: passez le curseur sur .some-class.hover

Testez ce code sur un ipad pour voir comment la méthode de survol de css et js se comporte différemment (en effet survol uniquement). Le bouton CSS n’a pas d’alerte de clic sophistiquée. http://jsfiddle.net/bensontrent/ctgr6stm/

 function clicker(id, doStuff) { id.on('touchstart', function(e) { id.addClass('hover'); }).on('touchmove', function(e) { id.removeClass('hover'); }).mouseenter(function(e) { id.addClass('hover'); }).mouseleave(function(e) { id.removeClass('hover'); }).click(function(e) { id.removeClass('hover'); //It's clicked. Do Something doStuff(id); }); } function doStuff(id) { //Do Stuff $('#clicked-alert').fadeIn(function() { $(this).fadeOut(); }); } clicker($('#unique-id'), doStuff); 
 button { display: block; margin: 20px; padding: 10px; -webkit-appearance: none; touch-action: manipulation; } .hover { background: yellow; } .btn:active { background: red; } .cssonly:hover { background: yellow; } .cssonly:active { background: red; } #clicked-alert { display: none; } 
   
This js method prevents hover from sticking on an ipad, and prevents the click registering as two clicks. In CSS, if you have any :hover in your css, change them to .hover For example .some-class:hover to .some-class.hover

Pour que les liens fonctionnent sans interrompre le défilement tactile, j’ai résolu ce problème avec l’événement “tap” de jQuery Mobile:

  $('a').not('nav.navbar a').on("tap", function () { var link = $(this).attr('href'); if (typeof link !== 'undefined') { window.location = link; } }); 

Je suis trop tard, je sais, mais c’est l’une des solutions les plus faciles que j’ai trouvé:

  $('body').on('touchstart','*',function(){ //listen to touch var jQueryElement=$(this); var element = jQueryElement.get(0); // find tapped HTML element if(!element.click){ var eventObj = document.createEvent('MouseEvents'); eventObj.initEvent('click',true,true); element.dispatchEvent(eventObj); } }); 

Cela ne fonctionne pas uniquement pour les liens (balises d’ancrage) mais également pour d’autres éléments. J’espère que cela t’aides.

Ce petit extrait semble fonctionner. Déclenchez l’événement click lorsque le lien est tapé:

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

Aucun de l’autre réponse ne fonctionne pour moi. Mon application a beaucoup d’écouteurs d’événements, de cases à cocher et de liens qui ont une écoute et des liens sans auditeur.

J’utilise ceci:

 var selector = "label, a, button"; var timer; var startX; var startY; $(document).on("click", selector, function (e) { if ($(this).data("touched") === true) { e.stopImmediatePropagation(); return false; } return; }).on("touchend", selector, function (e) { if (Math.abs(startX - e.originalEvent.changedTouches[0].screenX) > 10 || Math.abs(startY - e.originalEvent.changedTouches[0].screenY) > 10) // user action is not a tap return; var $this = $(this); // Visit: http://stackoverflow.com/questions/1694595/can-i-call-jquery-click-to-follow-an-a-link-if-i-havent-bound-an-event-hand/12801548#12801548 this.click(); // prevents double click $this.data("touched", true); if (timer) clearTimeout(timer); setTimeout(function () { $this.data("touched", false); }, 400); e.stopImmediatePropagation(); return false; }).on("touchstart", function (e) { startX = e.originalEvent.changedTouches[0].screenX; startY = e.originalEvent.changedTouches[0].screenY; }); 

Vous pouvez vérifier navigator.userAgent comme ceci:

 if(!navigator.userAgent.match(/iPhone/i) || !navigator.userAgent.match(/iPad/i)) { //bind your mouseovers... } 

mais vous devriez vérifier les mûres, les droïdes, les autres appareils à écran tactile. Vous pouvez également lier les survols de la souris uniquement si userAgent contient Mozilla, IE, Webkit ou Opera, mais que vous devez toujours rechercher certains périphériques car le Droid, par exemple, signale sa chaîne userAgent comme suit:

 Mozilla/5.0 (Linux; U; Android 2.0.1; en-us; Droid Build/ESD56) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 

La chaîne de l’iPhone est similaire. Si vous ne faites que filtrer pour iPhone, iPod, iPad, Android et Blackberry, vous pourriez obtenir la majorité des ordinateurs de poche, mais pas tous.

Créez simplement une requête média CSS qui exclut les tablettes et les appareils mobiles et placez le curseur dessus. Vous n’avez pas vraiment besoin de jQuery ou de JavaScript pour cela.

 @media screen and (min-device-width:1024px) { your-element:hover { /* Do whatever here */ } } 

Et assurez-vous d’append ceci à votre tête HTML pour vous assurer qu’il calcule avec les pixels réels et non la résolution.