Apontar para coisas na Web costumava ser simples. Você tinha um mouse, movia-o ou apertava botões, e só isso. Tudo o que não era um mouse foi emulado como um, e os desenvolvedores sabiam exatamente com o que contar.
No entanto, simples não significa necessariamente bom. Com o tempo, ficou cada vez mais importante que nem tudo era (ou fingiu ser) um mouse: era possível ter canetas sensíveis à pressão e inclinação, ter uma liberdade criativa incrível; você poderia usar os dedos, então só precisava do dispositivo e da sua mão; e por que não usar mais de um dedo enquanto está trabalhando?
Já temos eventos de toque para nos ajudar com isso, mas eles são uma API totalmente separada especificamente para toque, forçando você a codificar dois modelos de evento separados se quiser oferecer suporte a mouse e toque. O Chrome 55 tem 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.'));
Veja uma lista de todos os eventos disponíveis, que podem ser bastante familiares se você souber usar eventos de mouse:
pointerover
|
O ponteiro entrou na caixa delimitadora do elemento.
Isso acontece imediatamente em dispositivos que têm suporte ao recurso de passar o cursor ou antes de um
evento pointerdown para dispositivos que não oferecem suporte ao recurso.
|
pointerenter
|
Semelhante a pointerover , mas não gera bolhas e processa os descendentes de forma diferente.
Detalhes da especificação.
|
pointerdown
|
O ponteiro entrou no estado do botão ativo, com um botão pressionado ou estabelecido um contato, dependendo da semântica do dispositivo de entrada. |
pointermove
|
A posição do ponteiro foi alterada. |
pointerup
|
O ponteiro saiu do estado do botão ativo. |
pointercancel
|
Algo aconteceu, o que significa que é improvável que o ponteiro emita mais eventos. Isso significa que você precisa cancelar qualquer ação em andamento e voltar a 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 recurso de passar o cursor.
|
pointerleave
|
Semelhante a pointerout , mas não gera bolhas e processa os descendentes de forma diferente.
Detalhes da especificação.
|
gotpointercapture
|
O elemento recebeu uma captura de ponteiro. |
lostpointercapture
|
O ponteiro que estava sendo capturado foi liberado. |
Diferentes tipos de entrada
Geralmente, os eventos de ponteiro permitem escrever código de maneira independente de entrada,
sem a necessidade de registrar manipuladores de eventos separados para diferentes dispositivos de entrada.
Obviamente, você ainda precisará ficar atento às diferenças entre os tipos de entrada, por exemplo, se
o conceito de passar o cursor se aplica. No entanto, se você quiser diferenciar os tipos de dispositivo de entrada, talvez fornecendo
código/funcionalidade separados 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 toque ativado, certos gestos são usados para fazer a página rolar, dar zoom ou atualizar.
No caso de eventos de toque, você ainda receberá eventos enquanto essas ações
padrão estiverem ocorrendo. Por exemplo, touchmove
ainda será acionado enquanto o usuário estiver rolando a tela.
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 oferece melhor desempenho por padrão em comparação com os eventos de toque, em que você precisaria usar listeners de eventos passivos para ter 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 e iniciadas nesse 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
oferece suporte aos seguintes valores:
auto
|
Padrão: o navegador pode executar qualquer ação padrão. |
none
|
O navegador não tem permissão para executar nenhuma ação padrão. |
pan-x
|
O navegador só tem permissão para realizar a ação padrão de rolagem horizontal. |
pan-y
|
O navegador só tem permissão para executar a ação padrão de rolagem vertical. |
pan-left
|
O navegador só tem permissão para executar a ação padrão de rolagem horizontal e mover a página para a esquerda. |
pan-right
|
O navegador só tem permissão para realizar a ação padrão de rolagem horizontal e mover a página para a direita. |
pan-up
|
O navegador só tem permissão para executar a ação padrão de rolagem vertical e apenas mover a página para cima. |
pan-down
|
O navegador só tem permissão para executar a ação padrão de rolagem vertical e apenas mover a página para baixo. |
manipulation
|
O navegador só tem permissão para realizar ações de rolagem e zoom. |
Captura de ponteiro
Você já passou uma hora frustrante na depuração de um evento mouseup
corrompido até perceber que a causa disso é que o usuário está soltando o botão
fora do destino de clique? Não? Ok, talvez seja só eu, então.
Ainda assim, até agora, não havia uma boa maneira de lidar com esse problema. É claro que você pode configurar o gerenciador mouseup
no documento e salvar algum estado no aplicativo para acompanhar tudo. No entanto, essa não é a solução mais limpa, principalmente se você estiver criando um componente da Web e tentando manter tudo bom e
isolado.
Com os eventos de ponteiro, há uma solução muito melhor: você pode capturar o ponteiro
para ter certeza de que recebeu o evento pointerup
(ou qualquer outro dos
amigos esquivos dele).
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
Até o momento, os eventos de ponteiro são compatíveis com o Internet Explorer 11, Microsoft Edge, Chrome e Opera, e com suporte parcial no Firefox. Você encontra uma lista atualizada em caniuse.com.
Você pode usar o polyfill de eventos de ponteiro para preencher as lacunas. Como alternativa, a verificação do suporte a navegadores 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 ótimo candidato para o aprimoramento progressivo: basta
modificar os 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
.
Teste os recursos abaixo e nos conte o que achou.