¿Es posible forzar ignorar la pseudoclase: hover para usuarios de iPhone / iPad?

94

Tengo algunos menús css en mi sitio que se expanden con :hover(sin js)

Esto funciona de forma semi-rota en iDevices, por ejemplo, un toque activará la :hoverregla y expandirá el menú, pero luego tocar en otro lugar no elimina el :hover. Además, si hay un enlace dentro del elemento que está :hovereditado, debe tocar dos veces para activar el enlace (el primer toque desencadena :hover, el segundo toque desencadena el enlace).

He podido hacer que las cosas funcionen bien en el iPhone vinculando el touchstartevento.

¡El problema es que a veces el safari móvil todavía elige activar la :hoverregla desde el CSS en lugar de mis touchstarteventos!

Sé que este es el problema porque cuando desactivo todas las :hoverreglas manualmente en el CSS, el safari móvil funciona muy bien (pero los navegadores normales obviamente ya no lo hacen).

¿Hay alguna forma de "cancelar" dinámicamente las :hoverreglas para ciertos elementos cuando el usuario está en un safari móvil?

Vea y compare el comportamiento de iOS aquí: http://jsfiddle.net/74s35/3/ Nota: que solo algunas propiedades de CSS activan el comportamiento de dos clics, por ejemplo, display: none; pero no fondo: rojo; o decoración de texto: subrayado;

Christopher Camps
fuente
7
POR FAVOR NO desactive el desplazamiento del mouse solo por la presencia de eventos 'táctiles'. Esto afectará negativamente a los usuarios de computadoras portátiles que tengan dispositivos de entrada táctiles y de mouse. Vea mi respuesta para más detalles
Simon_Weaver

Respuestas:

77

Descubrí que ": hover" es impredecible en iPhone / iPad Safari. A veces, toca un elemento para hacer que ese elemento sea ": hover", mientras que a veces se desplaza a otros elementos.

Por el momento, solo tengo una clase de "no tocar" en el cuerpo.

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

Y tener todas las reglas CSS con ": hover" debajo de ".no-touch":

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

En algún lugar de la página, tengo javascript para eliminar la clase sin contacto del cuerpo.

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

Esto no parece perfecto, pero funciona de todos modos.

Morgan Cheng
fuente
5
Esta es la única forma de hacerlo. Lo que me molesta es que contamina el sitio "normal" con cosas que no pertenecen allí y son irrelevantes. Oh bien. Gracias.
Christopher Camps
También puede usar consultas de medios con un respaldo para IE
Lime
2
@Shackrock: No creo que el enfoque de "un toque para desplazarse y dos para hacer clic" sea una buena solución. Dado que, de hecho, no hay un desplazamiento real en un dispositivo táctil (hasta que vienen con pantallas que detectan que el dedo se desplaza sobre él), creo que es mejor no simular el desplazamiento en absoluto. Para efectos, como los comunes en botones y enlaces, simplemente omita el efecto. Para los menús que se expanden al pasar el mouse, haz que se expandan al tocar o hacer clic. Mi 2c.
Adrian Schmidt
18
Recientemente encontré problemas en este tipo de enfoque debido a los nuevos sistemas Windows 8 con entrada táctil y de mouse
Tom
4
+1 al comentario de Tom: la verificación de ontouchstart se volverá verdadera en las computadoras portátiles con capacidad táctil, incluso cuando estén conectadas a un monitor externo (donde es más deseable un sitio compatible con el mouse con estados de desplazamiento).
gregdev
45

:hoverno es el problema aquí. Safari para iOS sigue una regla muy extraña. Dispara mouseovery mousemoveprimero; si se cambia algo durante estos eventos, el 'clic' y los eventos relacionados no se disparan:

Diagrama de evento táctil en iOS

mouseentery mouseleaveparecen estar incluidos, aunque no se especifican en el gráfico.

Si modifica algo como resultado de estos eventos, los eventos de clic no se activarán. Eso incluye algo más arriba en el árbol DOM. Por ejemplo, esto evitará que los clics individuales funcionen en su sitio web con jQuery:

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

Editar: para aclarar, el hoverevento de jQuery incluye mouseentery mouseleave. Ambos evitarán clicksi se cambia el contenido.

Zenexer
fuente
5
por supuesto: el problema es el desplazamiento :-) o más bien la implementación defectuosa de Safari. Estos son antecedentes interesantes sobre el tema, pero Apple es la única parte que puede solucionar este terrible comportamiento. o debe "desplazarse" cuando se hace clic en algo fuera de los elementos, o nunca "desplazarse" en primer lugar. el comportamiento actual básicamente rompe cualquier sitio web que use: pasar el mouse por el mouse
Simon_Weaver
esto me ayudó a darme cuenta de que el hovercontrolador jquery estaba evitando que clickse golpeara a un controlador separado , ¡qué broma!
Simon_Weaver
1
Brillante respuesta y la mejor explicación del problema con el que me he encontrado. No son solo los desplazamientos CSS los que causan el problema del "doble clic", sino literalmente cualquier modificación del DOM después de los eventos de mouseover / mouseenter et al.
Oliver Joseph Ash
Aquí hay un ejemplo que muestra que el evento de clic no se mouseenter activa
Oliver Joseph Ash
@Oliver Joseph Ash Correcto, de ahí el ejemplo de JS en la respuesta. ;)
Zenexer
18

La biblioteca de detección de funciones del navegador Modernizer incluye una verificación de eventos táctiles.

Su comportamiento predeterminado es aplicar clases a su elemento html para cada característica que se detecta. A continuación, puede utilizar estas clases para diseñar su documento.

Si los eventos táctiles no están habilitados, Modernizr puede agregar una clase de no-touch:

<html class="no-touch">

Y luego amplíe sus estilos de desplazamiento con esta clase:

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

Puede descargar una compilación personalizada de Modernizr para incluir tantas o pocas detecciones de funciones como necesite.

Aquí hay un ejemplo de algunas clases que pueden aplicarse:

<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
fuente
Tenga en cuenta que en las versiones recientes de Chrome en computadoras de escritorio, Modernizr informa "toque". Esto aparentemente se corrige en la versión 3 con touchvents.
Craig Jacobs
18

Algunos dispositivos (como han dicho otros) tienen eventos táctiles y de mouse. Microsoft Surface, por ejemplo, tiene una pantalla táctil, un trackpad Y un lápiz que en realidad genera eventos de desplazamiento cuando se coloca sobre la pantalla.

Cualquier solución que se desactive en :hoverfunción de la presencia de eventos 'táctiles' también afectará a los usuarios de Surface (y a muchos otros dispositivos similares). Muchas computadoras portátiles nuevas son táctiles y responderán a eventos táctiles, por lo que deshabilitar el desplazamiento es una mala práctica.

Este es un error en Safari, no hay absolutamente ninguna justificación para este terrible comportamiento. Me niego a sabotear navegadores que no sean iOS debido a un error en iOS Safari que aparentemente ha estado allí durante años. Realmente espero que solucionen esto para iOS8 la próxima semana, pero mientras tanto ...

Mi solucion :

Algunos ya han sugerido usar Modernizr, bueno, Modernizr le permite crear sus propias pruebas. Básicamente, lo que estoy haciendo aquí es 'abstraer' la idea de un navegador compatible :hovercon una prueba de Modernizr que puedo usar en todo mi código sin codificación completa 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;
 });

Entonces el CSS se convierte en algo como esto

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

Solo en iOS esta prueba fallará y deshabilitará la sustitución.

La mejor parte de tal abstracción es que si encuentro que se rompe en un determinado Android o si está arreglado en iOS9, entonces puedo modificar la prueba.

Simon_Weaver
fuente
Esta solución es desagradable y la mejor que existe. Solo se rompe si un navegador que tiene problemas con el desplazamiento del mouse tiene tanto toque como clic, pero para los navegadores solo táctiles en ipad / iphone este no es el caso. Mi sugerencia es implementar esto solo como una solución de optimización (para eliminar el parpadeo de desplazamiento cuando ya se usa fastclick.js) y asegurarse de que todo también funcione sin él.
Gersom
Gracias. Estoy de acuerdo. Curioso por ver qué hace iOS 9 para este problema, en todo caso.
Simon_Weaver
Fui en la dirección opuesta:html:not(.no-hover) .category:hover { ...
Chris Marisic
+1 para "Safari no 'anuncia' al mundo que se comporta mal con: hover" Este problema hace que iOS sea mucho peor que cualquier versión de IE en mi opinión ...
Spencer O'Reilly
16

Agregar la biblioteca FastClick a su página hará que todos los toques en un dispositivo móvil se conviertan en eventos de clic (independientemente de dónde haga clic el usuario), por lo que también debería solucionar el problema de desplazamiento en dispositivos móviles. Edité tu violín como ejemplo: http://jsfiddle.net/FvACN/8/ .

Simplemente incluya la lib fastclick.min.js en su página y actívela a través de:

FastClick.attach(document.body);

Como beneficio adicional, también eliminará el molesto retraso de 300 ms al hacer clic que sufren los dispositivos móviles.


Hay un par de consecuencias menores al usar FastClick que pueden o no ser importantes para su sitio:

  1. Si toca en algún lugar de la página, se desplaza hacia arriba, hacia abajo y luego suelta el dedo en la misma posición exacta en la que lo colocó inicialmente, FastClick lo interpretará como un "clic", aunque obviamente no lo es. Al menos así es como funciona en la versión de FastClick que estoy usando actualmente (1.0.0). Es posible que alguien haya solucionado el problema desde esa versión.
  2. FastClick elimina la posibilidad de que alguien haga "doble clic".
Troya
fuente
1
¿Estaría dispuesto a compartir otro ejemplo concreto de cómo puedo hacer esto para mi sitio web? No sé nada sobre javascript, así que estoy un poco confundido acerca de cómo funcionaría esto. Tengo todos estos enlaces en mi sitio web sobre los que los usuarios suelen pasar el cursor antes de hacer clic, pero para ipad / móvil quiero hacerlo para que no tengan que hacer clic dos veces.
Andy
@Andy - No está exactamente claro lo que necesita y lo que se está perdiendo ... ¿Qué ha intentado hasta ahora y qué errores está viendo, si los hay? Tal vez sería mejor crear una nueva pregunta de Stackoverflow, si aún no lo ha hecho, con un ejemplo de lo que está tratando de hacer (?) O intente aclarar qué hay en el ejemplo anterior, jsfiddle que no tiene. no entiendo.
Troy
¿Se puede utilizar en aplicaciones de una sola página? Me refiero a cuando el contenido se actualiza cada vez que DOM
Sebastien Lorber
Sí, lo estoy usando con aplicaciones de una sola página.
Troy
16

Una mejor solución, sin ningún tipo de verificación JS, css class y viewport: puede usar Interaction Media Features (Media Queries Level 4)

Me gusta esto:

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

iOS Safari lo admite

Más sobre: https://www.jonathanfielding.com/an-introduction-to-interaction-media-features/

Estevão Lucas
fuente
Sin embargo, no es compatible con Firefox (en el momento de este comentario, por supuesto)
Frank
Esto ahora también es compatible con Firefox (FF 64+, lanzado en diciembre de 2018).
Miércoles
4

Básicamente, hay tres escenarios:

  1. El usuario solo tiene un dispositivo de mouse / puntero y puede activar:hover
  2. El usuario solo tiene una pantalla táctil y no puede activar :hoverelementos
  3. El usuario tiene tanto una pantalla táctil y un dispositivo de puntero

La respuesta aceptada originalmente funciona muy bien si solo son posibles los dos primeros escenarios, donde un usuario tiene un puntero o una pantalla táctil. Esto era común cuando el OP hizo la pregunta hace 4 años. Varios usuarios han señalado que los dispositivos Windows 8 y Surface hacen que el tercer escenario sea más probable.

La solución de iOS al problema de no poder desplazarse en los dispositivos con pantalla táctil (como lo detalla @Zenexer) es inteligente, pero puede hacer que el código sencillo se comporte mal (como lo señaló el OP). Desactivar el desplazamiento solo para dispositivos con pantalla táctil significa que aún deberá codificar una alternativa compatible con la pantalla táctil. Detectar cuándo un usuario tiene tanto el puntero como la pantalla táctil enturbia aún más las aguas (como lo explica @Simon_Weaver).

En este punto, la solución más segura es evitar el uso :hovercomo la única forma en que un usuario puede interactuar con su sitio web. Los efectos de desplazamiento son una buena forma de indicar que un enlace o botón es accionable, pero no se debería exigir al usuario que coloque el cursor sobre un elemento para realizar una acción en su sitio web.

Repensar la funcionalidad "hover" con pantallas táctiles en mente tiene una buena discusión sobre enfoques alternativos de UX. Las soluciones proporcionadas por la respuesta allí incluyen:

  • Reemplazo de menús flotantes con acciones directas (enlaces siempre visibles)
  • Reemplazo de menús flotantes con menús de toque
  • Mover grandes cantidades de contenido flotante a una página separada

En el futuro, esta será probablemente la mejor solución para todos los proyectos nuevos. La respuesta aceptada es probablemente la segunda mejor solución, pero asegúrese de tener en cuenta los dispositivos que también tienen dispositivos de puntero. Tenga cuidado de no eliminar la funcionalidad cuando un dispositivo tiene una pantalla táctil solo para evitar el :hoverhack de iOS .

sediento
fuente
1

La versión de JQuery en su .css usa .no-touch .my-element: hover para todas sus reglas de hover incluyen JQuery y el siguiente script

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

Luego, en la etiqueta del cuerpo, agregue class = "no-touch" ontouchstart = "removeHoverState ()"

tan pronto como ontouchstart se activa, se elimina la clase para todos los estados de desplazamiento

Greg
fuente
0

En lugar de tener solo efectos de desplazamiento cuando el toque no está disponible, creé un sistema para manejar eventos táctiles y eso me ha resuelto el problema. Primero, definí un objeto para probar eventos de "tap" (equivalente a "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;
      }
   }
};

Luego, en mi controlador de eventos hago algo como lo siguiente:

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

Gracias @Morgan Cheng por la respuesta, sin embargo, modifiqué ligeramente la función JS para obtener el " touchstart " (código tomado de la respuesta de @Timothy Perez ), sin embargo, necesita jQuery 1.7+ para esto

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

Dada la respuesta proporcionada por Zenexer, un patrón que no requiere etiquetas HTML adicionales es:

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

Este método dispara primero el mouseover, luego el clic.

muyfático
fuente
0

Estoy de acuerdo en que deshabilitar el desplazamiento del mouse para tocar es el camino a seguir.

Sin embargo, para evitarse la molestia de volver a escribir su css, simplemente envuelva los :hoverelementos en @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 joven
fuente
0

Para aquellos con un caso de uso común de deshabilitar :hovereventos en iOS Safari, la forma más sencilla es usar una consulta de medios de ancho mínimo para sus :hovereventos que permanece por encima del ancho de pantalla de los dispositivos que está evitando. Ejemplo:

@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
fuente
-6

Solo mire el tamaño de la pantalla ...

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

aquí está el código en el que querrás colocarlo

// 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
fuente