Introduzione
Il modello a eventi di JavaScript permette a una pagina web di reagire alle azioni dell’utente. Clic, pressioni sulla tastiera, movimenti del mouse e tocchi sullo schermo sono tutti esempi di eventi. In molti progetti moderni, però, distinguere tra mouse e touch può rendere il codice più lungo e difficile da mantenere.
La soluzione è rappresentata dai Pointer Events, un’API che unifica mouse, touchscreen e penne digitali attraverso un insieme comune di eventi. Invece di registrare listener separati per mousedown, touchstart e altri eventi, possiamo usare pointerdown, pointermove e pointerup.
In questo tutorial costruiremo una piccola area interattiva in cui l’utente può trascinare un elemento. L’esempio sarà adatto sia al mouse sia ai dispositivi touch e mostrerà concetti importanti come pointer capture, identificazione del dispositivo e gestione corretta del movimento.
Codice completo
Il seguente esempio può essere inserito in una pagina HTML. Il codice JavaScript si occupa di inizializzare il componente, ascoltare gli eventi e aggiornare la posizione dell’elemento trascinabile.
<div id="area" aria-label="Area per trascinare il quadrato">
<div id="quadrato" role="button" tabindex="0"></div>
</div>
<output id="stato" aria-live="polite">Pronto</output>
<script>
const area = document.querySelector(´#area´);
const quadrato = document.querySelector(´#quadrato´);
const stato = document.querySelector(´#stato´);
let posizione = { x: 40, y: 40 };
let trascinamento = null;
function aggiornaPosizione() {
quadrato.style.transform =
`translate(${posizione.x}px, ${posizione.y}px)`;
}
function iniziaTrascinamento(evento) {
// Consideriamo soltanto il pulsante principale del mouse.
if (evento.pointerType === ´mouse´ && evento.button !== 0) {
return;
}
const rettangolo = quadrato.getBoundingClientRect();
trascinamento = {
id: evento.pointerId,
offsetX: evento.clientX - rettangolo.left,
offsetY: evento.clientY - rettangolo.top
};
// Il quadrato continua a ricevere gli eventi anche
// se il puntatore esce dai suoi confini.
quadrato.setPointerCapture(evento.pointerId);
quadrato.classList.add(´in-movimento´);
stato.textContent = `Trascinamento avviato (${evento.pointerType})`;
evento.preventDefault();
}
function aggiornaTrascinamento(evento) {
if (!trascinamento || evento.pointerId !== trascinamento.id) {
return;
}
const areaRettangolo = area.getBoundingClientRect();
posizione.x = evento.clientX - areaRettangolo.left -
trascinamento.offsetX;
posizione.y = evento.clientY - areaRettangolo.top -
trascinamento.offsetY;
// Evita che il quadrato esca dall´area.
const limiteX = area.clientWidth - quadrato.offsetWidth;
const limiteY = area.clientHeight - quadrato.offsetHeight;
posizione.x = Math.max(0, Math.min(posizione.x, limiteX));
posizione.y = Math.max(0, Math.min(posizione.y, limiteY));
aggiornaPosizione();
}
function terminaTrascinamento(evento) {
if (!trascinamento || evento.pointerId !== trascinamento.id) {
return;
}
trascinamento = null;
quadrato.classList.remove(´in-movimento´);
stato.textContent = ´Trascinamento terminato´;
}
quadrato.addEventListener(´pointerdown´, iniziaTrascinamento);
quadrato.addEventListener(´pointermove´, aggiornaTrascinamento);
quadrato.addEventListener(´pointerup´, terminaTrascinamento);
quadrato.addEventListener(´pointercancel´, terminaTrascinamento);
aggiornaPosizione();
</script> Spiegazione
Gli eventi principali
pointerdown viene generato quando l’utente inizia un’interazione. Può corrispondere alla pressione del mouse, al contatto di un dito o all’uso di una penna.
pointermove viene generato quando il dispositivo si sposta. Nel nostro esempio aggiorna le coordinate del quadrato.
pointerup indica la fine dell’interazione. È importante gestire anche pointercancel, che può verificarsi quando il browser interrompe l’operazione, per esempio a causa di un cambio di orientamento o di un comportamento del sistema operativo.
Il ruolo di pointerId
Ogni puntatore possiede un identificatore numerico, disponibile nella proprietà pointerId. Salviamo questo valore all’inizio del trascinamento e ignoriamo gli eventi provenienti da un altro puntatore. Questa scelta diventa importante quando il dispositivo supporta interazioni contemporanee, come il multitouch.
Il ruolo di pointerType
La proprietà pointerType indica il tipo di dispositivo utilizzato. I valori più comuni sono mouse, touch e pen. In questo esempio la proprietà viene mostrata all’utente, ma può anche essere usata per modificare il comportamento dell’interfaccia.
Pointer capture
Quando l’utente trascina rapidamente il quadrato, il puntatore potrebbe uscire dai suoi confini. La chiamata setPointerCapture() garantisce che l’elemento continui a ricevere gli eventi associati a quel puntatore fino alla conclusione dell’operazione.
Senza il pointer capture, il trascinamento potrebbe interrompersi in modo imprevisto, soprattutto su elementi piccoli o durante movimenti veloci.
Perché usare preventDefault
Durante un’interazione touch, il browser potrebbe interpretare il gesto come scorrimento della pagina. La chiamata a preventDefault() evita comportamenti indesiderati nel momento in cui inizia il trascinamento. In un’applicazione reale è inoltre utile definire nel CSS la proprietà touch-action sull’area interattiva, così da dichiarare esplicitamente quali gesti devono essere gestiti dal browser.
Best practice
- Preferisci Pointer Events per componenti multipiattaforma: un unico insieme di listener riduce duplicazioni e differenze tra mouse, touch e penna.
- Gestisci sempre pointercancel: l’interazione può essere interrotta anche senza un normale
pointerup. - Controlla pointerId: evita che eventi appartenenti a interazioni diverse modifichino lo stesso componente.
- Usa pointer capture per il drag: è particolarmente utile quando l’elemento è piccolo o il movimento è rapido.
- Non aggiornare inutilmente il layout: per spostare l’elemento è preferibile usare
transform, spesso più efficiente rispetto alla modifica continua ditopeleft. - Prevedi l’accessibilità: aggiungi un nome accessibile, uno stato comunicabile e una modalità alternativa da tastiera quando l’interazione è essenziale.
- Considera il CSS: la regola
touch-action: nonepuò impedire lo scrolling durante un trascinamento, ma va applicata soltanto dove è realmente necessaria.
Riepilogo
I Pointer Events offrono un modello uniforme per gestire mouse, touch e penna. Gli eventi pointerdown, pointermove, pointerup e pointercancel permettono di costruire componenti interattivi senza duplicare la logica per ogni dispositivo.
Per realizzare un trascinamento affidabile è utile conservare l’identificatore del puntatore, calcolare correttamente l’offset iniziale, limitare la posizione all’area disponibile e usare setPointerCapture(). Queste tecniche sono applicabili a slider, elementi spostabili, canvas, mappe e interfacce touch.
