web-dev-qa-db-fra.com

Désactiver les effets de survol sur les navigateurs mobiles

J'écris un site Web destiné à être utilisé à la fois sur des ordinateurs de bureau et des tablettes. Lors de la visite à partir d'un bureau, je souhaite que les zones cliquables de l'écran s'allument avec des effets :hover (couleur d'arrière-plan différente, etc.) Avec une tablette, il n'y a pas de souris, je ne souhaite donc aucun effet de survol.

Le problème, c’est que, lorsque je tape quelque chose sur la tablette, le navigateur a évidemment une sorte de "curseur de souris invisible" qui se déplace à l’emplacement que j’ai tapé, puis le laisse là-bas. Effet de vol stationnaire jusqu'à ce que je tape quelque chose d'autre.

Comment puis-je obtenir les effets de survol lorsque j'utilise la souris, mais les supprimer lorsque j'utilise l'écran tactile?

Au cas où quelqu'un songerait à le suggérer, je ne souhaite pas utiliser le reniflement utilisateur-agent. Le même appareil pourrait avoir à la fois un écran tactile et une souris (peut-être moins courant de nos jours, mais beaucoup plus à l'avenir). Je ne m'intéresse pas à l'appareil, mais à son utilisation actuelle: souris ou écran tactile.

J'ai déjà essayé de suspendre les événements touchstart, touchmove et touchend et d'appeler preventDefault() sur chacun d'entre eux, ce qui supprime parfois le "curseur invisible de la souris"; mais si je tape rapidement entre deux éléments différents, après quelques tapotements, il commence à déplacer le "curseur de la souris" et à éclairer les effets de survol de toute façon - c'est comme si ma preventDefault n'était pas toujours honorée. Je ne vous ennuierai pas avec les détails sauf si nécessaire - je ne suis même pas sûr que ce soit la bonne approche à adopter; si quelqu'un a une solution plus simple, je suis tout ouïe.


Edit: Ceci peut être reproduit avec CSS :hover, mais voici un rapide repro pour référence.

<style>
  .box { border: 1px solid black; width: 150px; height: 150px; }
  .box:hover { background: blue; }
</style>
<div class="box"></div>
<div class="box"></div>

Si vous passez la souris sur l'une des cases, vous obtiendrez un arrière-plan bleu, ce que je veux. Mais si vous appuyez sur l'une des cases, le fond bleu s'affichera également, ce que j'essaie d'éviter.

J'ai également posté un exemple ici qui fait ce qui précède et qui accroche également les événements de souris de jQuery. Vous pouvez l'utiliser pour voir que les événements de tap déclencheront également mouseenter, mousemove et mouseleave.

110
Joe White

Je déduis de votre question que votre effet de survol change le contenu de votre page. Dans ce cas, mon conseil est de:

  • Ajoute des effets de survol sur touchstart et mouseenter.
  • Suppression des effets de survol sur mouseleave, touchmove et click.

Alternativement, vous pouvez éditer votre page pour qu’il n’y ait pas de changement de contenu.

Contexte

Afin de simuler une souris, des navigateurs tels que Webkit mobile déclenchent les événements suivants lorsqu'un utilisateur touche et relâche un doigt sur un écran tactile (comme un iPad) (source: Touch And Mouse sur html5rocks.com):

  1. touchstart
  2. touchmove
  3. touchend
  4. 300 ms de retard, lorsque le navigateur s'assure qu'il s'agit d'un simple tap, pas d'un double tap
  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 d'indiquer simplement au navigateur Web de sauter les événements de la souris.

Pire, si un événement mouseover modifie le contenu de la page, l'événement click ne sera jamais déclenché, comme expliqué dans le document Guide du contenu Web Safari - Gestion des événements , en particulier la figure 6.4 dans Événements à un doigt. Ce qui est exactement un "changement de contenu", dépendra du navigateur et de la version. J'ai constaté 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 survol sur touchstart et mouseenter.
  • Supprimez les effets de survol sur mouseleave, touchmove et click.

Notez qu'il n'y a aucune action sur touchend!

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

Si l'utilisateur clicks un lien, l'effet de survol est également supprimé. Cela garantit qu'il est supprimé si l'utilisateur appuie sur le bouton de retour dans le navigateur Web.

Cela fonctionne également pour les événements tactiles: sur touchstart, l'effet de survol est ajouté. Il est '' 'pas' '' enlevé au bout des doigts. 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 n'ait à cliquer à nouveau!

Le retard de 300 ms d’un navigateur entre un événement touchstart et click est en réalité bien 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 comme d'habitude. Normalement, c'est un problème car aucun événement mouseleave n'est déclenché et l'effet de survol reste en place. Heureusement, cela peut être facilement résolu en supprimant l'effet de survol sur touchmove.

C'est tout!

Notez qu'il est possible de supprimer le délai de 300 ms, par exemple à l'aide de la bibliothèque FastClick , mais cette question est hors de portée.

Solutions alternatives

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

  • détection du navigateur: Extrêmement sujet aux erreurs. Supposons qu'un périphérique dispose de la souris ou du toucher, tandis qu'une combinaison des deux deviendra de plus en plus courante lorsque les écrans tactiles prolifèrent.
  • Détection de média CSS: La seule solution CSS que je connaisse. Toujours sujet aux erreurs et supposant toujours qu'un périphérique dispose de la souris ou du toucher, les deux sont possibles.
  • Émule l'événement click dans touchend: Cela ne suivra pas correctement le lien, même si l'utilisateur ne souhaitait que faire défiler ou zoomer, sans avoir l'intention de cliquer sur le lien.
  • Utilisez une variable pour supprimer les événements de souris: Ceci définit une variable dans touchend qui est utilisée comme condition if dans les événements de souris suivants pour empêcher les changements d'état à ce moment précis. La variable est réinitialisée dans l'événement click. Voir la réponse de Walter Roman sur cette page. C'est une solution décente si vous ne voulez vraiment pas d'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 zoomé), et essaie par la suite de suivre le lien avec une souris (c'est-à-dire sur un périphérique avec les deux interface).

Lectures complémentaires

81
MacFreek

Comment puis-je obtenir les effets de survol lorsque j'utilise la souris, mais les supprimer lorsque j'utilise l'écran tactile?

Peut-être n'y pensez-vous pas autant que de supprimer les effets de survol pour les écrans tactiles, mais plutôt d'ajouter des effets de survol pour les événements de souris?

Si vous souhaitez conserver les effets :hover dans votre CSS, vous pouvez spécifier différents styles pour différents médias:

@media screen { /* hover styles here */ } 

@media handheld { /* non-hover styles here */ }

Sauf que malheureusement, de nombreux appareils mobiles l'ignorent et utilisent simplement les règles de l'écran. Heureusement, de nombreux navigateurs pour tablettes et mobiles récents prennent en charge des requêtes multimédia plus sophistiquées:

@media screen and (max-width:800px) { /* non-hover styles here */ }

Ainsi, même si la partie "screen" ou "handheld" est ignorée, "max-width" fera l'affaire pour vous. Vous pouvez simplement supposer que tout ce qui a un écran de moins de 800 pixels doit être une tablette ou un téléphone et ne pas utiliser d’effets de survol. Pour les rares utilisateurs qui utilisent une souris sur un périphérique basse résolution, ils ne verraient pas les effets de survol, mais votre site irait bien autrement.

En savoir plus sur les requêtes des médias? Il existe de nombreux articles sur ce sujet en ligne - en voici un: http://www.alistapart.com/articles/retour-de-la-mobile-stylesheet

Si vous décalez les effets de survol de votre CSS et que vous les appliquez avec JavaScript, vous pouvez vous lier spécifiquement aux événements de souris et/ou encore, vous pouvez simplement émettre des hypothèses uniquement en fonction de la taille de l'écran, le pire problème L'utilisateur qui utilise une souris manque les effets de survol.

19
nnnnnn

J'ai écrit le JS suivant pour un projet récent, un site pour ordinateur de bureau/mobile/tablette doté d'effets de survol qui ne devraient pas apparaître au toucher.

Le module mobileNoHoverState ci-dessous contient une variable preventMouseover (initialement déclarée false), définie sur true lorsqu'un utilisateur déclenche l'événement touchstart sur un élément, $target

preventMouseover est ensuite redéfini sur false chaque fois que l'événement mouseover est déclenché, ce qui permet au site de fonctionner comme prévu si un utilisateur utilise à la fois son écran tactile et sa souris.

Nous savons que mouseover est déclenché après touchstart en raison de l'ordre dans lequel elles sont déclarées dans init.

var mobileNoHoverState = function() {

    var hoverClass = 'hover',
        $target = $(".foo"), 
        preventMouseover = false;

    function forTouchstart() {
        preventMouseover = true;
    }

    function forMouseover() {
        if (preventMouseover === false) {
            $(this).addClass(hoverClass);
        } else {
            preventMouseover = false;
        }
    }

    function forMouseout() {
        $(this).removeClass(hoverClass);
    }

    function init() {
        $target.on({
            touchstart  : forTouchstart,
            mouseover   : forMouseover,
            mouseout    : forMouseout
        });                
    }

    return {
        init: init
    };
}();

Le module est ensuite instancié plus loin dans la ligne:

mobileNoHoverState.init();
9
Walter Roman

Je voulais vraiment une solution css pure à ce sujet, car asperger d’une solution javascript pesante autour de tous mes points de vue me semblait une option déplaisante. Enfin trouvé la requête @ media.hover , qui peut détecter "si le mécanisme de saisie principal permet à l'utilisateur de survoler les éléments". Cela évite les périphériques tactiles où "survoler" est davantage une action émulée qu'une capacité directe du périphérique d'entrée.

Donc, par exemple, si j'ai un lien:

<a href="/" class="link">Home</a>

Ensuite, je peux en toute sécurité le nommer uniquement en :hover lorsque l'appareil le prend facilement en charge avec cette css:

@media (hover: hover) {
  .link:hover { /* hover styles */ }
}

Alors que la plupart des navigateurs modernes prennent en charge les requêtes de fonctionnalité de média d'interaction, certains navigateurs populaires tels que IE et Firefox ne le font pas. Dans mon cas, cela fonctionne bien, car je voulais uniquement prendre en charge Chrome sur le bureau et Chrome et Safari sur le mobile. 

5
flintinatux

Ma solution consiste à ajouter la classe css hover-active à la balise HTML, Et de l’utiliser au début de tous les sélecteurs CSS avec: hover

http://codepen.io/Bnaya/pen/EoJlb

JS:

(function () {
    'use strict';

    if (!('addEventListener' in window)) {
        return;
    }

    var htmlElement = document.querySelector('html');

    function touchStart () {
        document.querySelector('html').classList.remove('hover-active');

        htmlElement.removeEventListener('touchstart', touchStart);
    }

    htmlElement.addEventListener('touchstart', touchStart);
}());

HTML:

<html class="hover-active">

CSS:

.hover-active .mybutton:hover {
    box-shadow: 1px 1px 1px #000;
}
5
Bnaya

Ce que j’ai fait pour résoudre le même problème est d’avoir une fonctionnalité de détection (j’utilise quelque chose comme: ce code ), en vérifiant si onTouchMove est défini et, dans ce cas, j’ajoute la classe CSS "touchMode" au corps, sinon j'ajoute "desktopMode".

Ensuite, chaque fois qu'un effet de style s'applique uniquement à un périphérique tactile ou à un ordinateur de bureau, la règle css est précédée de la classe appropriée:

.desktopMode .someClass:hover{ color: red }
.touchMode .mainDiv { width: 100%; margin: 0; /*etc.*/ }

Edit: Cette stratégie ajoute bien sûr quelques caractères supplémentaires à votre fichier CSS. Par conséquent, si vous êtes préoccupé par la taille du fichier CSS, vous pouvez rechercher les définitions de touchMode et desktopMode et les placer dans différents fichiers afin de pouvoir les servir de manière optimale. css pour chaque type d'appareil; ou vous pouvez changer les noms de classe en quelque chose de beaucoup plus court avant de passer à la prod.

2

Dans mon projet, nous avons résolu ce problème en utilisant https://www.npmjs.com/package/postcss-hover-prefix et https://modernizr.com/ D'abord, nous post-traitons générer des fichiers css avec postcss-hover-prefix. Il ajoute .no-touch pour toutes les règles css hover.

const fs = require("fs");
const postcss = require("postcss");
const hoverPrfx = require("postcss-hover-prefix");

var css = fs.readFileSync(cssFileName, "utf8").toString();
postcss()
   .use(hoverPrfx("no-touch"))
   .process(css)
   .then((result) => {
      fs.writeFileSync(cssFileName, result);
   });

css

a.text-primary:hover {
  color: #62686d;
}

devient

.no-touch a.text-primary:hover {
  color: #62686d;
}

Au moment de l'exécution, Modernizr ajoute automatiquement les classes css à la balise html comme ceci

<html class="wpfe-full-height js flexbox flexboxlegacy canvas canvastext webgl 
  no-touch 
  geolocation postmessage websqldatabase indexeddb hashchange
  history draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage
  borderradius boxshadow textshadow opacity cssanimations csscolumns cssgradients
  cssreflections csstransforms csstransforms3d csstransitions fontface
  generatedcontent video audio localstorage sessionstorage webworkers
  applicationcache svg inlinesvg smil svgclippaths websocketsbinary">

Ce post-traitement de css plus Modernizr désactive le survol pour les appareils tactiles et permet à d’autres. En fait, cette approche a été inspirée par Bootstrap 4 pour résoudre le même problème: https://v4-alpha.getbootstrap.com/getting-started/browsers-devices/#sticky-hoverfocus-on-mobile

2
Stanislav Berkov

Vous pouvez déclencher l'événement mouseLeave chaque fois que vous touchez un élément sur l'écran tactile. Voici une solution pour toutes les balises <a>:

function removeHover() {
    var anchors = document.getElementsByTagName('a');
    for(i=0; i<anchors.length; i++) {
        anchors[i].addEventListener('touchstart', function(e){
            $('a').mouseleave();
        }, false);
    }
}
1
Said Kholov

Oui, jst eu un problème similaire mais j’ai réussi à le résoudre avec des requêtes multimédia et des CSS simples. Je suis sûr que je ne respecte pas certaines règles, mais cela fonctionne pour moi.

En gros, je devais accepter une demande massive et la rendre réactive. Ils ont utilisé jQueryUI et m'ont demandé de ne pas toucher à l'un de leurs jQuery. Je ne pouvais donc utiliser que CSS.

Lorsque j'ai appuyé sur l'un de leurs boutons en mode écran tactile, l'effet de survol s'est déclenché pendant une seconde avant que l'action du bouton ne prenne effet. Voici comment je l'ai corrigé.

@media only screen and (max-width:1024px) {

       #buttonOne{
            height: 44px;
        }


        #buttonOne:hover{
            display:none;
        }
}   
1
Daft

Iv'd a trouvé 2 solutions au problème, ce qui implique que vous détectiez le contact avec modernizr ou quelque chose d'autre et que vous définissiez une classe de contact sur l'élément html.

C'est bien, mais pas pris en charge très bien:

html.touch *:hover {
    all:unset!important;
}

Mais cela a un très bon support:

html.touch *:hover {
    pointer-events: none !important;
}

Cela fonctionne parfaitement pour moi, cela fait en sorte que tous les effets de survol soient comme lorsque vous appuyez sur un bouton, il s’allume mais ne finit pas en buggy comme effet de survol initial pour les événements de souris.

Détecter le toucher à partir d'appareils sans contact, je pense que modernizr a fait le meilleur travail possible:

https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js </ strike>

MODIFIER

J'ai trouvé une solution meilleure et plus simple à ce problème

Comment déterminer si le client est un périphérique tactile

1
Simon Pertersen

Incluez Modernizr sur votre page et définissez vos états de survol comme ceci:

html.no-touchevents .box:hover {
    background: blue;
}
0
Jonathan Potter

Voici ma solution: http://jsfiddle.net/agamemnus/g56aw709/-- code ci-dessous.

Tout ce qu'il faut faire, c'est convertir leur ": survol" en "survol" ... c'est tout! La grande différence entre ceci et le reste est que cela fonctionnera également sur des sélecteurs d'éléments non singuliers tels que .my_class > *:hover {.

handle_css_hover_effects ()

function handle_css_hover_effects (init) {
 var init = init || {}
 var handle_touch_events = init.handle_touch_events || true
 var handle_mouse_events = init.handle_mouse_events || true
 var hover_class         = init.hover_class         || "hover"
 var delay_preferences   = init.delay_preferences   || {touch: {add: 500, remove: 500}}
 function default_handler (curobj, input_type, op) {
  var hovered_element_selector = "*" + ((op == "add") ? ":" : ("." + hover_class))
  var hovered_elements = Array.prototype.slice.call(document.body.querySelectorAll(hovered_element_selector))
  var modified_list = []
  while (true) {
   if ((curobj == null) || (curobj == document.documentElement)) break
   if (hovered_elements.indexOf(curobj) != -1) modified_list.Push (curobj)
   curobj = curobj.parentNode
  }
  function do_hover_change () {modified_list.forEach (function (curobj) {curobj.classList[op](hover_class)})}
  if ((!delay_preferences[input_type]) || (!delay_preferences[input_type][op])) {
   do_hover_change ()
  } else {
   setTimeout (do_hover_change, delay_preferences[input_type][op])
  }
 }

 if (handle_mouse_events) {
  document.body.addEventListener ('mouseover' , function (evt) {var curobj = evt.target; default_handler (curobj, "mouse", "add")})
  document.body.addEventListener ('mouseout'  , function (evt) {var curobj = evt.target; default_handler (curobj, "mouse", "remove")})
  document.body.addEventListener ('click'     , function (evt) {var curobj = evt.target; default_handler (curobj, "mouse", "remove")})
 }

 if (handle_touch_events) {
  document.body.addEventListener ('touchstart', function (evt) {var curobj = evt.target; default_handler (curobj, "touch", "add")})
  document.body.addEventListener ('touchend'  , function (evt) {var curobj = evt.target; default_handler (curobj, "touch", "remove")})
  document.body.addEventListener ('touchmove',  function (evt) {
   var curobj = evt.target
   var hovered_elements = Array.prototype.slice.call(document.body.querySelectorAll("*:hover"))
   var lastobj = null
   evt = evt.changedTouches[0]
   var elements_at_point = get_elements_at_point (evt.pageX, evt.pageY)
   // Get the last element that isn't at the current point but is still hovered over, and remove only its hover attribute.
   while (true) {
    if ((curobj == null) || (curobj == document.documentElement)) break
    if ((hovered_elements.indexOf(curobj) != -1) && (elements_at_point.indexOf(curobj) == -1)) lastobj = curobj
    curobj = curobj.parentNode
   }
   if (lastobj == null) return
   if ((!delay_preferences.touch) || (!delay_preferences.touch.remove)) {
    lastobj.classList.remove(hover_class)
   } else {
    setTimeout (function () {lastobj.classList.remove(hover_class)}, delay_preferences.touch.remove)
   }

   function get_elements_at_point (x, y) {
    var el_list = [], pe_list = []
    while (true) {
     var curobj = document.elementFromPoint(x, y)
     if ((curobj == null) || (curobj == document.documentElement)) break
     el_list.Push (curobj); pe_list.Push (curobj.style.pointerEvents)
     curobj.style.pointerEvents = "none"
    }
    el_list.forEach (function (current_element, i) {current_element.style.pointerEvents = pe_list[i]})
    return el_list
   }
  })
 }
}
0
Agamemnus

Il peut être utile de consulter votre code CSS, car cela semble être un problème plutôt étrange. Quoi qu'il en soit, si cela se produit et que tout le reste est bon, vous pouvez essayer de décaler l'effet survol en javascript (vous pouvez également utiliser jquery) . Simplement, liez-vous à l'événement mouseenter ou mieux encore mouseenter et illuminez votre élément lorsque l'événement se déclenche.

Consultez le dernier exemple ici: http://api.jquery.com/mouseover/ , vous pouvez utiliser quelque chose de similaire pour vous connecter lorsque l'événement se déclenche et le prendre à partir de là!

0
balach

Dans un projet que j'ai mené récemment, j'ai résolu ce problème avec la fonction des événements délégués de jQuery. Il recherche certains éléments à l'aide d'un sélecteur jQuery et ajoute/supprime une classe CSS à ces éléments lorsque la souris survole l'élément. Cela semble bien fonctionner dans la mesure où j'ai pu le tester, y compris IE10 sur un ordinateur portable doté de la technologie tactile fonctionnant sous Windows 8.

$(document).ready(
    function()
    {
        // insert your own selector here: maybe '.hoverable'?
        var selector = 'button, .hotspot';

        $('body')
            .on('mouseover', selector, function(){ $(this).addClass('mouseover');    })
            .on('mouseout',  selector, function(){ $(this).removeClass('mouseover'); })
            .on('click',     selector, function(){ $(this).removeClass('mouseover'); });
    }
);

edit: Cette solution nécessite bien sûr de modifier votre code CSS pour supprimer les sélecteurs ": hover", et de déterminer à l’avance quels éléments vous souhaitez "survoler".

Si votre page contient de nombreux éléments (plusieurs milliers par exemple), elle risque d’être un peu lente, car cette solution capture des événements de trois types sur tous les éléments de la page, puis fait son effet si le sélecteur allumettes. J'ai nommé la classe CSS "mouseover" au lieu de "hover", car je ne voulais pas que les lecteurs de CSS lisent ": hover" là où j'ai écrit ". Hover".

0
toon81

Bonjour, personne du futur, vous voudrez probablement utiliser la requête multimédia pointer et/ou hover. La requête multimédia handheld était obsolète.

/* device is using a mouse or similar */
@media (pointer: fine) {
  a:hover {
    background: red;
  }
}
0
olanod

Si vous préférez utiliser JavaScript, vous pouvez utiliser Modernizr dans votre page. Lors du chargement de la page, la classe ".no-touch" est ajoutée à la balise html dans un navigateur d'écran non tactile, mais la balise html est associée à la classe ".touch". . 

Ensuite, il suffit simplement de vérifier si la balise html a la classe no-touch avant de décider d’ajouter votre auditeur mouseenter et mouseleave.

if($('html').hasClass('no-touch')){
    $('.box').on("mouseenter", function(event){
            $(this).css('background-color','#0000ff')
    });
    $('.box').on("mouseleave", function(event){
            $(this).css('background-color','')
    });
}

Pour un appareil à écran tactile, les événements ne seront pas écoutés, vous ne recevrez donc aucun effet de survol lorsque vous appuyez sur.

0
Stanz