Apontar para coisas na Web era simples. Você tinha um mouse, movia-o para lá e para cá, às vezes pressionava botões e pronto. Tudo que não era um mouse era emulado como um, e os desenvolvedores sabiam exatamente o que esperar.
Simples não significa necessariamente bom. Com o tempo, tornou-se cada vez mais importante que nem tudo fosse (ou fingindo ser) um mouse: você podia ter canetas sensíveis à pressão e à inclinação, para uma liberdade criativa incrível. Você podia usar os dedos, então tudo o que você precisava era o dispositivo e a mão. E por que não usar mais de um dedo enquanto você está nisso?
Já temos eventos de toque há algum tempo para ajudar com isso, mas eles são uma API totalmente separada especificamente para toque, forçando você a programar dois modelos de evento separados se quiser oferecer suporte a mouse e toque. O Chrome 55 vem com um padrão mais recente que unifica os dois modelos: eventos de ponteiro.
Um modelo de evento único
Os eventos de ponteiro unificam o modelo de entrada de ponteiro para o navegador, reunindo toques, canetas e mouses em um único conjunto de eventos. Exemplo:
document.addEventListener('pointermove',
ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
ev => console.log('The pointer is now over foo.'));
Confira uma lista de todos os eventos disponíveis, que devem parecer bastante familiares se você já conhece os eventos do mouse:
pointerover
|
O ponteiro entrou na caixa delimitadora do elemento.
Isso acontece imediatamente para dispositivos com suporte ao passar o cursor ou antes de um
evento pointerdown para dispositivos sem suporte.
|
pointerenter
|
Semelhante a pointerover , mas não flutua e processa
descendentes de maneira diferente.
Detalhes sobre a especificação.
|
pointerdown
|
O ponteiro entrou no estado de botão ativo, com um botão sendo pressionado ou o contato sendo estabelecido, dependendo da semântica do dispositivo de entrada. |
pointermove
|
O ponteiro mudou de posição. |
pointerup
|
O ponteiro deixou o estado do botão ativo. |
pointercancel
|
Algo aconteceu e é improvável que o ponteiro emita mais eventos. Isso significa que você precisa cancelar todas as ações em andamento e voltar para um estado de entrada neutro. |
pointerout
|
O ponteiro saiu da caixa delimitadora do elemento ou da tela. Também após um
pointerup , se o dispositivo não oferecer suporte ao passar o cursor.
|
pointerleave
|
Semelhante a pointerout , mas não flutua e processa
descendentes de maneira diferente.
Detalhes sobre a especificação.
|
gotpointercapture
|
O elemento recebeu a captura de ponteiro. |
lostpointercapture
|
O ponteiro que estava sendo capturado foi liberado. |
Diferentes tipos de entrada
Em geral, os eventos de ponteiro permitem que você escreva código de forma independente da entrada,
sem precisar registrar manipuladores de eventos separados para diferentes dispositivos de entrada.
É claro que você ainda precisa estar atento às diferenças entre os tipos de entrada, como se
o conceito de passar o cursor se aplica. Se você quiser diferenciar diferentes tipos de dispositivos de entrada, talvez para fornecer
código/funcionalidade separada para entradas diferentes, faça isso
nos mesmos manipuladores de eventos usando a propriedade pointerType
da
interface
PointerEvent
. Por exemplo, se você estivesse codificando uma gaveta de navegação lateral, poderia
ter a seguinte lógica no evento pointermove
:
switch(ev.pointerType) {
case 'mouse':
// Do nothing.
break;
case 'touch':
// Allow drag gesture.
break;
case 'pen':
// Also allow drag gesture.
break;
default:
// Getting an empty string means the browser doesn't know
// what device type it is. Let's assume mouse and do nothing.
break;
}
Ações padrão
Em navegadores com recursos de toque, alguns gestos são usados para rolar, aplicar zoom ou atualizar a página.
No caso de eventos de toque, você ainda vai receber eventos enquanto essas ações
padrão estiverem ocorrendo. Por exemplo, touchmove
ainda será acionado enquanto o usuário estiver rolando.
Com eventos de ponteiro, sempre que uma ação padrão como rolagem ou zoom for acionada,
você receberá um evento pointercancel
para informar que o navegador assumiu
o controle do ponteiro. Exemplo:
document.addEventListener('pointercancel',
ev => console.log('Go home, the browser is in charge now.'));
Velocidade integrada: esse modelo permite um desempenho melhor por padrão, em comparação com eventos de toque, em que você precisa usar listeners de eventos passivos para alcançar o mesmo nível de capacidade de resposta.
É possível impedir que o navegador assuma o controle com a
propriedade CSS
touch-action
. Definir como none
em um elemento desativa todas
as ações definidas pelo navegador iniciadas sobre esse elemento. No entanto, há vários
outros valores para controle mais refinado, como pan-x
, para permitir
que o navegador reaja ao movimento no eixo x, mas não no eixo y. O Chrome 55
é compatível com os seguintes valores:
auto
|
Padrão: o navegador pode realizar qualquer ação padrão. |
none
|
O navegador não tem permissão para realizar ações padrão. |
pan-x
|
O navegador só pode realizar a ação padrão de rolagem horizontal. |
pan-y
|
O navegador só pode executar a ação padrão de rolagem vertical. |
pan-left
|
O navegador só pode executar a ação padrão de rolagem horizontal e só pode mover a página para a esquerda. |
pan-right
|
O navegador só pode executar a ação padrão de rolagem horizontal e só pode mover a página para a direita. |
pan-up
|
O navegador só pode executar a ação padrão de rolagem vertical e apenas para mover a página para cima. |
pan-down
|
O navegador só pode executar a ação padrão de rolagem vertical e apenas para mover a página para baixo. |
manipulation
|
O navegador só pode executar ações de rolagem e zoom. |
Captura de ponteiro
Já passou uma hora frustrante depurando um evento mouseup
quebrado até perceber que o usuário estava soltando o botão
fora do alvo de clique? Não? Talvez seja só eu.
Ainda assim, até agora não havia uma maneira muito boa de resolver esse problema. Claro,
você pode configurar o gerenciador mouseup
no documento e salvar um estado no
aplicativo para acompanhar as coisas. Essa não é a solução mais limpa,
mas é uma boa opção se você estiver criando um componente da Web e tentando manter tudo bem
isolado.
Com os eventos do ponteiro, há uma solução muito melhor: é possível capturar o ponteiro
para garantir que você receba o evento pointerup
(ou qualquer outro
dos amigos evasivos).
const foo = document.querySelector('#foo');
foo.addEventListener('pointerdown', ev => {
console.log('Button down, capturing!');
// Every pointer has an ID, which you can read from the event.
foo.setPointerCapture(ev.pointerId);
});
foo.addEventListener('pointerup',
ev => console.log('Button up. Every time!'));
Suporte ao navegador
No momento da redação deste artigo, os eventos de ponteiro têm suporte no Internet Explorer 11, Microsoft Edge, Chrome e Opera, e têm suporte parcial no Firefox. Confira uma lista atualizada em caniuse.com.
Você pode usar o polyfill de eventos de ponteiro para preencher as lacunas. Como alternativa, a verificação de suporte do navegador no momento da execução é simples:
if (window.PointerEvent) {
// Yay, we can use pointer events!
} else {
// Back to mouse and touch events, I guess.
}
Os eventos de ponteiro são um candidato fantástico para melhoria progressiva: basta
modificar seus métodos de inicialização para fazer a verificação acima, adicionar manipuladores de eventos de ponteiro
no bloco if
e mover os manipuladores de eventos de mouse/toque para o
bloco else
.
Então, faça um teste e conte o que achou!