Isso é o que eu venho até agora depois de estudar o restante das respostas. Ele deve ser capaz de oferecer suporte a usuários híbridos, apenas com toque, somente mouse.
Crie uma classe de foco separado para o efeito de foco. Por padrão, adicione essa classe de foco ao nosso botão.
Não queremos detectar a presença de suporte por toque e desativar todos os efeitos de foco instantâneo desde o início. Como mencionado por outros, os dispositivos híbridos estão ganhando popularidade; as pessoas podem ter suporte ao toque, mas desejam usar o mouse e vice-versa. Portanto, remova a classe de foco apenas quando o usuário realmente tocar no botão.
O próximo problema é: e se o usuário quiser voltar a usar o mouse depois de tocar no botão? Para resolver isso, precisamos encontrar um momento oportuno para adicionar de volta a classe hover que removemos.
No entanto, não podemos adicioná-lo de volta imediatamente após removê-lo, porque o estado de foco ainda está ativo. Podemos não querer destruir e recriar o botão inteiro também.
Então, pensei em usar um algoritmo de espera ocupada (usando setInterval) para verificar o estado de foco. Depois que o estado de foco é desativado, podemos adicionar novamente a classe de foco e interromper a espera ocupada, retornando ao estado original em que o usuário pode usar o mouse ou o toque.
Sei que esperar muito ocupado não é tão bom, mas não tenho certeza se há um evento apropriado. Eu considerei adicioná-lo novamente no evento mouseleave, mas não foi muito robusto. Por exemplo, quando um alerta é exibido depois que o botão é tocado, a posição do mouse muda, mas o evento do mouse não é acionado.
var button = document.getElementById('myButton');
button.ontouchstart = function(e) {
console.log('ontouchstart');
$('.button').removeClass('button-hover');
startIntervalToResetHover();
};
button.onclick = function(e) {
console.log('onclick');
}
var intervalId;
function startIntervalToResetHover() {
// Clear the previous one, if any.
if (intervalId) {
clearInterval(intervalId);
}
intervalId = setInterval(function() {
// Stop if the hover class already exists.
if ($('.button').hasClass('button-hover')) {
clearInterval(intervalId);
intervalId = null;
return;
}
// Checking of hover state from
// http://stackoverflow.com/a/8981521/2669960.
var isHovered = !!$('.button').filter(function() {
return $(this).is(":hover");
}).length;
if (isHovered) {
console.log('Hover state is active');
} else {
console.log('Hover state is inactive');
$('.button').addClass('button-hover');
console.log('Added back the button-hover class');
clearInterval(intervalId);
intervalId = null;
}
}, 1000);
}
.button {
color: green;
border: none;
}
.button-hover:hover {
background: yellow;
border: none;
}
.button:active {
border: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id='myButton' class='button button-hover'>Hello</button>
Edit: Outra abordagem que tentei é chamar e.preventDefault()
no ontouchstart ou ontouchend. Parece interromper o efeito de foco quando o botão é tocado, mas também interrompe a animação do clique no botão e evita que a função onclick seja chamada quando o botão é tocado, para que você precise chamá-los manualmente no manipulador ontouchstart ou ontouchend. Não é uma solução muito limpa.