Apontar para o futuro

Sérgio Gomes

Apontar para as coisas na Web costumava ser simples. Você tinha um mouse, você moveu às vezes você 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, ser simples não significa necessariamente bom. Com o passar do tempo, ela se tornou importante que nem tudo era (ou alegou ser) um rato: você poderia ter canetas sensíveis à pressão e que reconhecem a inclinação, para exercer uma incrível liberdade criativa; você poderia você só precisava do dispositivo e da sua mão, e por que usar apenas um dedo?

Já tivemos eventos de toque para nos ajudar nisso por um tempo, mas são uma API totalmente separada. especificamente para toque, forçando-o a codificar dois modelos de evento separados se você quer 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 único modelo de evento

Os eventos de ponteiro unificam o modelo de entrada de ponteiro para o navegador, unindo toque, 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.'));

Aqui está uma lista de todos os eventos disponíveis, que deve ser bastante familiar se Se você já conhece os eventos do mouse:

pointerover O ponteiro entrou na caixa delimitadora do elemento. Isso acontece imediatamente em dispositivos compatíveis com o recurso de passar o cursor ou antes de uma pointerdown para dispositivos que não têm.
pointerenter Semelhante a pointerover, mas não gera balões e alças descendentes de maneira diferente. Detalhes sobre as especificações.
pointerdown O ponteiro entrou no estado do botão ativo, com um botão sendo pressionado ou o contato estabelecido, dependendo da semântica do dispositivo de entrada.
pointermove A posição do ponteiro mudou.
pointerup O ponteiro saiu do estado do botão ativo.
pointercancel Aconteceu algo que significa que é improvável que o ponteiro emita algum mais eventos. Isso significa que você deve cancelar todas as ações em andamento 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 for compatível com o recurso de passar o cursor.
pointerleave Semelhante a pointerout, mas não gera balões e alças descendentes de maneira diferente. Detalhes sobre as especificações.
gotpointercapture O elemento recebeu a captura de ponteiro.
lostpointercapture O ponteiro que estava sendo capturado foi lançado.

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 dispositivos de entrada diferentes. É claro que você ainda precisará prestar atenção às diferenças entre os tipos de entrada, como se aplica-se o conceito de passar o cursor. Se você não quiser diferenciar os tipos de dispositivos de entrada, talvez para fornecer código/funcionalidade separados para entradas diferentes. No entanto, é possível fazer isso nos mesmos manipuladores de eventos usando a propriedade pointerType do PointerEvent interface gráfica do usuário. Por exemplo, se você estivesse codificando uma gaveta de navegação lateral, poderia use 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 habilitados para toque, alguns gestos são usados para fazer a página rolar, aplicar zoom ou atualizar. No caso de eventos de toque, você ainda receberá eventos, enquanto essas ações estão ocorrendo. Por exemplo, touchmove ainda será acionado enquanto o usuário estiver rolando a tela.

Com os eventos de ponteiro, sempre que uma ação padrão, como rolagem ou zoom, é acionada, você vai receber um evento pointercancel para informar que o navegador 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 os eventos de toque, em que você precisaria usar listeners de eventos passivos para atingir o mesmo nível de capacidade de resposta.

Você pode impedir que o navegador assuma o controle com a touch-action propriedade CSS. Defini-lo como none em um elemento desativará todos ações definidas pelo navegador iniciadas nesse elemento. Mas existem várias outros valores para um controle mais refinado, como pan-x, para permitir o navegador reagir ao movimento no eixo x, mas não no eixo y. Chrome 55 suporta 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ó tem permissão para executar 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 apenas mover a página para a esquerda.
pan-right O navegador só tem permissão para executar 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 depurando um mouseup corrompido até perceber que é porque o usuário está soltando o botão fora da sua meta de cliques? Não? Certo, talvez só eu, então.

Ainda assim, até agora não havia uma maneira muito boa de lidar com esse problema. Claro, é possível configurar o gerenciador mouseup no documento e salvar um estado no seu aplicativo para acompanhar tudo. Essa não é a solução mais ecológica, especialmente se você estiver criando um componente da Web e tentando manter tudo agradável e isolados.

Com os eventos de ponteiro, há uma solução muito melhor: você pode capturar o ponteiro, para garantir que você receba o evento pointerup (ou qualquer outro evento amigos).

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

Atualmente, os eventos de ponteiro são compatíveis com o Internet Explorer 11, Microsoft Edge, Chrome e Opera, e parcialmente compatíveis com o Firefox. Você pode encontre uma lista atualizada em caniuse.com.

Você pode usar o polyfill de eventos de ponteiro para preencher as lacunas. Como alternativa, verificar o suporte do navegador no ambiente de execução é direta:

if (window.PointerEvent) {
    // Yay, we can use pointer events!
} else {
    // Back to mouse and touch events, I guess.
}

Os eventos de ponteiro são ótimos candidatos ao aprimoramento progressivo: modificar os métodos de inicialização para fazer a verificação acima, adicionar evento de ponteiro no bloco if e mova os manipuladores de eventos do mouse/toque para a else.

Experimente e nos conte o que você achou.