Indicare la strada da seguire

Sérgio Gomes

In passato puntare a determinati elementi sul web era semplice. Avevi un mouse, lo spostavi e a volte premi i pulsanti. E questo è tutto. Tutto ciò che non era un mouse veniva emulato come tale e gli sviluppatori sapevano esattamente su cosa contare.

Semplicità, però, non significa necessariamente che sia utile. Nel corso del tempo, è diventato sempre più importante che non tutto fosse (o finto di essere) un mouse: si potevano avere penne sensibili alla pressione e inclinate per avere una straordinaria libertà creativa. Potevi usare le dita, quindi tutto ciò che ti servivano erano il dispositivo e la mano. Inoltre, perché non utilizzare più di un dito mentre ci stai avvicinando?

È da un po' che ci occupiamo degli eventi touch, ma si tratta di API completamente separate e specifiche per il tocco, pertanto devi codificare due modelli di eventi separati se vuoi supportare sia il mouse sia il tocco. Chrome 55 è dotato di uno standard più recente che unifica entrambi i modelli: gli eventi di puntatore.

Un singolo modello a evento

Gli eventi di puntamento unificano il modello di input del puntatore per il browser, riunendo tocco, penne e mouse in un unico insieme di eventi. Ad esempio:

document.addEventListener('pointermove',
    ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
    ev => console.log('The pointer is now over foo.'));

Di seguito è riportato un elenco di tutti gli eventi disponibili, che dovrebbero esserti familiari se hai dimestichezza con gli eventi del mouse:

pointerover Il puntatore è entrato nel riquadro di delimitazione dell'elemento. Ciò avviene immediatamente per i dispositivi che supportano il passaggio del mouse o prima di un evento pointerdown per i dispositivi che non lo supportano.
pointerenter Simile a pointerover, ma non mostra le bolle e gestisce i discendenti in modo diverso. Dettagli sulle specifiche.
pointerdown Il puntatore è entrato nello stato del pulsante attivo e può essere premuto un pulsante o stabilito un contatto, a seconda della semantica del dispositivo di input.
pointermove Il puntatore ha cambiato posizione.
pointerup Il puntatore ha lasciato lo stato del pulsante attivo.
pointercancel Si è verificato un problema che significa che è improbabile che il puntatore emetta altri eventi. Ciò significa che devi annullare tutte le azioni in corso e tornare a uno stato di input neutro.
pointerout Il puntatore ha lasciato il riquadro di delimitazione dell'elemento o della schermata. Sempre dopo pointerup, se il dispositivo non supporta il passaggio del mouse.
pointerleave Simile a pointerout, ma non mostra le bolle e gestisce i discendenti in modo diverso. Dettagli sulle specifiche.
gotpointercapture L'elemento ha ricevuto l'acquisizione del puntatore.
lostpointercapture Il puntatore che era in fase di acquisizione è stato rilasciato.

Diversi tipi di input

In genere, gli eventi puntatore consentono di scrivere codice in modo indipendente dall'input, senza dover registrare gestori di eventi separati per dispositivi di input diversi. Ovviamente devi comunque fare attenzione alle differenze tra i tipi di input, ad esempio se viene applicato il concetto di passaggio del mouse. Se vuoi distinguere i diversi tipi di dispositivi di input, magari per fornire codice/funzionalità separati per input diversi, puoi farlo all'interno degli stessi gestori di eventi utilizzando la proprietà pointerType dell'interfaccia PointerEvent. Ad esempio, se stai programmando un riquadro di navigazione a scomparsa laterale, potresti avere la seguente logica sul tuo 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;
}

Azioni predefinite

Nei browser con funzionalità touch, alcuni gesti vengono utilizzati per far scorrere, eseguire lo zoom o aggiornare la pagina. In caso di eventi tocco, continuerai a ricevere gli eventi mentre queste azioni predefinite sono in corso. Ad esempio, touchmove continuerà a essere attivato mentre l'utente scorre.

Con gli eventi puntatore, ogni volta che viene attivata un'azione predefinita come scorrimento o zoom, viene visualizzato un evento pointercancel che indica che il browser ha acquisito il controllo del puntatore. Ad esempio:

document.addEventListener('pointercancel',
    ev => console.log('Go home, the browser is in charge now.'));

Velocità integrata: questo modello consente di migliorare le prestazioni per impostazione predefinita rispetto agli eventi touch, in cui avresti bisogno di utilizzare i Listener di eventi passivi per ottenere lo stesso livello di reattività.

Puoi impedire al browser di assumere il controllo della proprietà CSS touch-action. Se il criterio viene impostato su none per un elemento, tutte le azioni definite dal browser avviate su quell'elemento verranno disattivate. Tuttavia, esistono una serie di altri valori per un controllo più granulare, ad esempio pan-x, per consentire al browser di reagire a movimenti sull'asse x, ma non sull'asse y. Chrome 55 supporta i seguenti valori:

auto Predefinita; il browser può eseguire qualsiasi azione predefinita.
none Il browser non è autorizzato a eseguire azioni predefinite.
pan-x Il browser è autorizzato a eseguire soltanto l'azione predefinita di scorrimento orizzontale.
pan-y Il browser può eseguire soltanto l'azione predefinita di scorrimento verticale.
pan-left Il browser può eseguire soltanto l'azione predefinita di scorrimento orizzontale e solo eseguire la panoramica della pagina verso sinistra.
pan-right Il browser può eseguire soltanto l'azione predefinita di scorrimento orizzontale e solo eseguire la panoramica della pagina verso destra.
pan-up Il browser può eseguire soltanto l'azione predefinita di scorrimento verticale e solo la panoramica della pagina verso l'alto.
pan-down Il browser può eseguire soltanto l'azione predefinita di scorrimento verticale e solo eseguire la panoramica della pagina verso il basso.
manipulation Il browser può eseguire soltanto azioni di scorrimento e zoom.

Acquisizione con puntatore

Ti è mai capitato di dedicare un'ora frustrante al debug di un evento mouseup non funzionante, finché non ti sei reso conto che è perché l'utente sta lasciando andare il pulsante al di fuori del tuo target di clic? No? Ok, allora forse sono solo io.

Tuttavia, finora non c'era un modo molto efficace per affrontare questo problema. Certo, potresti configurare il gestore mouseup sul documento e salvare uno stato nell'applicazione per tenere traccia delle cose. Non è la soluzione più pulita, ma in particolare se stai creando un componente web e cercando di mantenere tutto in ordine e isolato.

Con gli eventi di puntatore si tratta di una soluzione molto migliore: puoi acquisire il puntatore, in modo da essere sicuro di ottenere l'evento pointerup (o qualsiasi altro suo sfuggenti amici).

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!'));

Supporto del browser

Al momento, gli eventi puntatore sono supportati in Internet Explorer 11, Microsoft Edge, Chrome e Opera, nonché parzialmente supportati in Firefox. Puoi trovare un elenco aggiornato all'indirizzo caniuse.com.

Puoi utilizzare il polyfill degli eventi puntatore per compilare gli spazi vuoti. In alternativa, verificare il supporto del browser in fase di runtime è semplice:

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

Gli eventi puntatore sono ideali per il miglioramento progressivo: basta modificare i metodi di inizializzazione per eseguire il controllo qui sopra, aggiungere gestori di eventi puntatore nel blocco if e spostare i gestori di eventi mouse/touch nel blocco else.

Dai un'occhiata al canale e facci sapere cosa ne pensi.