É possível forçar a ignorar a: pseudoclasse hover para usuários de iPhone / iPad?

94

Tenho alguns menus css no meu site que se expandem com :hover(sem js)

Isso funciona de forma semi-quebrada em iDevices, por exemplo, um toque ativará a :hoverregra e expandirá o menu, mas tocar em outro lugar não remove o :hover. Além disso, se houver um link dentro do elemento que está :hoverinserido, você deve tocar duas vezes para ativar o link (o primeiro toque aciona :hover, o segundo toque aciona o link).

Consegui fazer as coisas funcionarem bem no iphone vinculando o touchstartevento.

O problema é que às vezes o safari móvel ainda escolhe acionar a :hoverregra do css em vez de meus touchstarteventos!

Eu sei que este é o problema porque quando eu desabilito todas as :hoverregras manualmente no css, o safari móvel funciona muito bem (mas os navegadores regulares obviamente não funcionam mais).

Existe uma maneira de "cancelar" :hoverregras dinamicamente para certos elementos quando o usuário está em um safári móvel?

Veja e compare o comportamento do iOS aqui: http://jsfiddle.net/74s35/3/ Observação: apenas algumas propriedades css acionam o comportamento de dois cliques, por exemplo, display: none; mas não fundo: vermelho; ou decoração de texto: sublinhado;

Christopher Camps
fonte
7
POR FAVOR, NÃO desative o pairar apenas por causa da presença de eventos de 'toque'. Isso afetará adversamente os usuários de laptops que possuem dispositivos de entrada de toque e mouse. Veja minha resposta para mais detalhes
Simon_Weaver

Respostas:

77

Descobri que ": hover" é imprevisível no Safari do iPhone / iPad. Às vezes, tocar em um elemento torna esse elemento ": pairar", enquanto às vezes ele muda para outros elementos.

Por enquanto, eu só tenho uma aula "sem toque" no corpo.

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

E tenha todas as regras CSS com ": hover" abaixo de ".no-touch":

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

Em algum lugar da página, tenho javascript para remover a classe no-touch do corpo.

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

Isso não parece perfeito, mas funciona de qualquer maneira.

Morgan Cheng
fonte
5
Essa é a única maneira de fazer isso. O que me incomoda é que polui o site "normal" com coisas que não pertencem a ele e são irrelevantes. Ah bem. Obrigado.
Christopher Camps de
Você também pode usar consultas de mídia com um substituto para o IE
Lime
2
@Shackrock: Não acho que a abordagem "um toque para pairar e dois para clicar" seja uma boa solução. Como não há pairar real em um dispositivo de toque (até que eles venham com telas que detectem seu dedo pairando sobre ele), acho melhor nem simular o pairar. Para efeitos, como aqueles comuns em botões e links, basta pular o efeito. Para menus que se expandem ao passar o mouse, faça-os expandir ao tocar / clicar. Meu 2c.
Adrian Schmidt
18
Recentemente, encontrei problemas neste tipo de abordagem devido aos novos sistemas Windows 8 com entrada de toque e mouse
Tom
4
+1 para o comentário de Tom - a verificação de ontouchstart retornará verdadeiro em laptops com capacidade de toque, mesmo quando conectados a um monitor externo (onde um site compatível com o mouse com estados de foco é mais desejável).
Gregdev
45

:hovernão é o problema aqui. Safari para iOS segue uma regra muito estranha. Ele dispara mouseovere mousemoveprimeiro; se algo for alterado durante esses eventos, 'clique' e os eventos relacionados não serão disparados:

Diagrama de evento de toque no iOS

mouseentere mouseleaveparecem estar incluídos, embora não sejam especificados no gráfico.

Se você modificar qualquer coisa como resultado desses eventos, os eventos de clique não serão disparados. Isso inclui algo mais acima na árvore DOM. Por exemplo, isso impedirá que cliques únicos funcionem em seu site com jQuery:

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

Edit: Para esclarecimento, o hoverevento do jQuery inclui mouseentere mouseleave. Ambos irão prevenir clickse o conteúdo for alterado.

Zenexer
fonte
5
claro: hover é o problema :-) ou melhor, a implementação com erros do Safari dele. este é um histórico interessante sobre o problema, mas a Apple é a única parte que pode consertar esse comportamento terrível. ele precisa 'revelar' quando algo fora dos elementos é clicado ou nunca 'pairar' em primeiro lugar. o comportamento atual basicamente quebra qualquer site que use: passar o mouse por um mouse
Simon_Weaver
isso me ajudou a perceber que o hovermanipulador jquery estava evitando que um clickmanipulador separado fosse atingido - que piada!
Simon_Weaver
1
Resposta brilhante e a melhor explicação para o problema que encontrei. Não são apenas os hovers CSS que causam o problema do "clique duplo", mas literalmente qualquer modificação do DOM após os eventos mouseover / mouseenter et al.
Oliver Joseph Ash,
Aqui está um exemplo que mostra o evento de clique não disparando quando o DOM é modificado em mouseenter jsbin.com/maseti/5/edit?html,js,output
Oliver Joseph Ash
@Oliver Joseph Ash Certo, daí o exemplo JS na resposta. ;)
Zenexer
18

O Modernizer da biblioteca de detecção de recursos do navegador inclui uma verificação de eventos de toque.

Seu comportamento padrão é aplicar classes ao seu elemento html para cada recurso detectado. Você pode então usar essas classes para definir o estilo do seu documento.

Se os eventos de toque não estiverem ativados, a Modernizr pode adicionar uma classe de no-touch:

<html class="no-touch">

E então escopo seus estilos de foco com esta classe:

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

Você pode baixar uma versão customizada do Modernizr para incluir o mínimo ou o número de detecções de recursos que você precisar.

Aqui está um exemplo de algumas classes que podem ser aplicadas:

<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
fonte
Esteja ciente de que nas versões recentes do Chrome em desktops, o Modernizr relata "toque". Isso foi aparentemente corrigido na versão 3 com touchevents.
Craig Jacobs
18

Alguns dispositivos (como outros já disseram) têm eventos de toque e mouse. O Microsoft Surface, por exemplo, tem uma tela sensível ao toque, um trackpad E uma caneta que realmente gera eventos de foco quando é pairado acima da tela.

Qualquer solução que desabilite com :hoverbase na presença de eventos de 'toque' também afetará os usuários do Surface (e muitos outros dispositivos semelhantes). Muitos novos laptops são sensíveis ao toque e responderão a eventos de toque - então, desativar o foco é uma prática realmente ruim.

Este é um bug no Safari, não há absolutamente nenhuma justificativa para este terrível comportamento. Recuso-me a sabotar navegadores que não sejam iOS por causa de um bug no iOS Safari que aparentemente existe há anos. Eu realmente espero que eles corrijam isso para iOS8 na próxima semana, mas enquanto isso ...

Minha solução :

Alguns já sugeriram o uso do Modernizr, bem, o Modernizr permite que você crie seus próprios testes. O que estou basicamente fazendo aqui é 'abstrair' a ideia de um navegador compatível :hovercom um teste Modernizr que posso usar em todo o meu código sem codificar totalmente 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;
 });

Então o css se torna algo assim

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

Apenas no iOS esse teste falhará e desabilitará o rollover.

A melhor parte dessa abstração é que se eu descobrir que ele quebra em um determinado Android ou se for corrigido no iOS9, posso apenas modificar o teste.

Simon_Weaver
fonte
Esta solução é desagradável e a melhor que existe. Ele só será interrompido se um navegador com dificuldade de pairar no mouse tiver toque e clique, mas para os navegadores somente de toque no ipad / iphone esse não é o caso. Minha sugestão é implementar isso apenas como uma correção de otimização (para remover a oscilação do mouse quando já estiver usando fastclick.js) e certificar-se de que tudo também funcione sem ele.
Gersom
Obrigado. Concordo. Curioso para ver o que o iOS 9 faz para esse problema - se houver alguma coisa.
Simon_Weaver
Eu fui na direção oposta:html:not(.no-hover) .category:hover { ...
Chris Marisic,
+1 para "O Safari não 'anuncia' para o mundo que se comporta mal com: hover" Este problema torna o iOS muito pior do que qualquer versão do IE, na minha opinião ...
Spencer O'Reilly
16

Adicionar a biblioteca FastClick à sua página fará com que todos os toques em um dispositivo móvel sejam transformados em eventos de clique (independentemente de onde o usuário clica), portanto, também deve corrigir o problema de foco em dispositivos móveis. Eu editei seu violino como exemplo: http://jsfiddle.net/FvACN/8/ .

Basta incluir a lib fastclick.min.js em sua página e ativar via:

FastClick.attach(document.body);

Como um benefício colateral, ele também removerá o atraso onClick irritante de 300ms que os dispositivos móveis sofrem.


Existem algumas consequências menores no uso do FastClick que podem ou não ser importantes para o seu site:

  1. Se você tocar em algum lugar da página, rolar para cima, rolar de volta para baixo e, em seguida, soltar o dedo exatamente na mesma posição em que o colocou inicialmente, o FastClick interpretará isso como um "clique", embora obviamente não seja. Pelo menos é assim que funciona na versão do FastClick que estou usando atualmente (1.0.0). Alguém pode ter corrigido o problema desde essa versão.
  2. FastClick remove a capacidade de alguém "clicar duas vezes".
Troy
fonte
1
Você estaria disposto a compartilhar outro exemplo concreto de como posso fazer isso no meu site. Eu não sei nada sobre javascript, então estou um pouco confuso sobre como isso funcionaria. Tenho todos esses links em meu site que os usuários costumam passar o mouse antes de clicar, mas para ipad / celular quero fazer com que eles não precisem clicar duas vezes.
Andy,
@Andy - Não está exatamente claro o que você precisa e o que está faltando ... O que você tentou, até agora, e quais erros você está vendo, se houver? Talvez seja melhor criar uma nova pergunta Stackoverflow, se ainda não o fez, com um exemplo do que você está tentando fazer (?) Ou tente esclarecer o que é no exemplo acima, jsfiddle que você não não entendo.
Troy,
isso pode ser usado em aplicativos de página única? Quero dizer, quando o conteúdo DOM é atualizado sempre
Sebastien Lorber
Sim, estou usando-o com aplicativos de página única.
Troy
16

Uma solução melhor, sem qualquer JS, classe css e verificação de janela de visualização: você pode usar os recursos do Interaction Media (Media Queries Nível 4)

Como isso:

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

iOS Safari suporta

Mais sobre: https://www.jonathanfielding.com/an-introduction-to-interaction-media-features/

Estevão lucas
fonte
Não suportado pelo Firefox (no momento deste comentário, é claro)
Frank
Agora, isso também é compatível com o Firefox (FF 64+, lançado em dezembro de 2018).
wunch de
4

Existem basicamente três cenários:

  1. O usuário tem apenas um dispositivo de mouse / ponteiro e pode ativar:hover
  2. O usuário possui apenas uma tela sensível ao toque e não pode ativar os :hoverelementos
  3. O usuário tem tanto um ecrã táctil e um dispositivo apontador

A resposta aceita originalmente funciona muito bem se apenas os dois primeiros cenários forem possíveis, em que um usuário tem um ponteiro ou uma tela sensível ao toque. Isso era comum quando o OP fez a pergunta há 4 anos. Vários usuários apontaram que os dispositivos Windows 8 e Surface estão tornando o terceiro cenário mais provável.

A solução iOS para o problema de não ser capaz de pairar em dispositivos touchscreen (conforme detalhado por @Zenexer) é inteligente, mas pode fazer com que um código simples não funcione corretamente (conforme observado pelo OP). Desativar o foco apenas para dispositivos com tela de toque significa que você ainda precisará codificar uma alternativa amigável com tela de toque. Detectar quando um usuário tem o ponteiro e a tela sensível ao toque turva ainda mais as águas (conforme explicado por @Simon_Weaver).

Neste ponto, a solução mais segura é evitar o uso :hovercomo a única maneira de um usuário interagir com seu site. Os efeitos de focalização são uma boa maneira de indicar que um link ou botão pode ser acionado, mas o usuário não deve ser obrigado a passar o mouse sobre um elemento para executar uma ação em seu site.

Repensar a funcionalidade “hover” com telas sensíveis ao toque em mente traz uma boa discussão sobre abordagens alternativas de UX. As soluções fornecidas pela resposta incluem:

  • Substituir menus suspensos por ações diretas (links sempre visíveis)
  • Substituindo menus instantâneos por menus instantâneos
  • Movendo grandes quantidades de conteúdo instantâneo para uma página separada

No futuro, esta provavelmente será a melhor solução para todos os novos projetos. A resposta aceita é provavelmente a segunda melhor solução, mas certifique-se de levar em consideração os dispositivos que também possuem dispositivos apontadores. Tenha cuidado para não eliminar a funcionalidade quando um dispositivo tem uma tela sensível ao toque apenas para contornar o :hoverhack do iOS .

terceiro
fonte
1

A versão JQuery em seu .css usa .no-touch .my-element: hover para todas as suas regras de hover incluem JQuery e o seguinte script

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

Em seguida, no corpo da tag, adicione class = "no-touch" ontouchstart = "removeHoverState ()"

assim que o ontouchstart dispara, a classe para todos os estados de foco é removida

Greg
fonte
0

Em vez de apenas ter efeitos de flutuação quando o toque não está disponível, criei um sistema para lidar com eventos de toque e isso resolveu o problema para mim. Primeiro, defini um objeto para teste de eventos de "toque" (equivalente a "clique").

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

Então, em meu manipulador de eventos, faço algo como o seguinte:

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

Obrigado @Morgan Cheng pela resposta, no entanto, modifiquei ligeiramente a função JS para obter o " touchstart " (código retirado da resposta de @Timothy Perez ), no entanto, você precisa do jQuery 1.7+ para isso

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

Dada a resposta fornecida pelo Zenexer, um padrão que não requer tags HTML adicionais é:

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 primeiro o mouseover, depois o clique.

muitofático
fonte
0

Concordo que desativar o foco para toque é o caminho a percorrer.

No entanto, para evitar o trabalho de reescrever seu css, basta embrulhar todos os :hoveritens @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
fonte
0

Para aqueles com o caso de uso comum de desabilitar :hovereventos no iOS Safari, a maneira mais simples é usar uma consulta de mídia de largura mínima para seus :hovereventos que fica acima da largura da tela dos dispositivos que você está evitando. Exemplo:

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

Basta olhar para o tamanho da tela ....

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

aqui está o código em que você deseja colocá-lo

// 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
}
Lucas
fonte