Aggiungi il tocco al tuo sito

I touchscreen sono disponibili su un numero sempre maggiore di dispositivi, dai telefoni agli schermi dei computer. La tua app dovrebbe rispondere al loro tocco in modi belli e intuitivi.

Matt Gaunt

I touchscreen sono disponibili su un numero sempre maggiore di dispositivi, dai telefoni agli schermi dei computer. Se gli utenti scelgono di interagire con la tua UI, l'app deve rispondere al loro tocco in modo intuitivo.

Rispondere agli stati degli elementi

Ti è mai capitato di toccare o fare clic su un elemento di una pagina web e chiederti se il sito lo ha effettivamente rilevato?

La semplice modifica del colore di un elemento quando gli utenti toccano o interagiscono con parti dell'interfaccia utente possono essere certi che il sito funzioni correttamente. Ciò non solo attenua la frustrazione, ma può anche dare una sensazione di freschezza e reattività.

Gli elementi DOM possono ereditare uno qualsiasi dei seguenti stati: predefinito, attivo, al passaggio del mouse e attivo. Per modificare la UI per ciascuno di questi stati, dobbiamo applicare gli stili alle seguenti pseudoclassi :hover, :focus e :active, come mostrato di seguito:

.btn {
  background-color: #4285f4;
}

.btn:hover {
  background-color: #296cdb;
}

.btn:focus {
  background-color: #0f52c1;

  /* The outline parameter suppresses the border
  color / outline when focused */
  outline: 0;
}

.btn:active {
  background-color: #0039a8;
}

Prova

Immagine che illustra i diversi colori
degli stati dei pulsanti

Nella maggior parte dei browser mobile, gli stati hover e/o hover vengono applicati a un elemento dopo che è stato toccato.

Valuta con attenzione gli stili che imposti e come appariranno all'utente una volta terminato il tocco.

Eliminazione degli stili predefiniti del browser

Dopo aver aggiunto stili per i diversi stati, noterai che la maggior parte dei browser implementa i propri stili in risposta al tocco dell'utente. Il motivo è in gran parte perché quando i dispositivi mobili sono stati lanciati per la prima volta, alcuni siti non avevano lo stile per lo stato :active. Di conseguenza, molti browser hanno aggiunto un ulteriore colore di evidenziazione o uno stile per fornire agli utenti un feedback.

La maggior parte dei browser utilizza la proprietà CSS outline per visualizzare un cerchio intorno a un elemento quando lo stato attivo è attivo. Puoi eliminarlo con:

.btn:focus {
    outline: 0;

    /* Add replacement focus styling here (i.e. border) */
}

Safari e Chrome aggiungono un colore di evidenziazione del tocco che può essere impedito con la proprietà CSS -webkit-tap-highlight-color:

/* Webkit / Chrome Specific CSS to remove tap
highlight color */
.btn {
  -webkit-tap-highlight-color: transparent;
}

Prova

Internet Explorer su Windows Phone ha un comportamento simile, ma viene eliminato tramite un meta tag:

<meta name="msapplication-tap-highlight" content="no">

Firefox ha due effetti collaterali da gestire.

La pseudo classe -moz-focus-inner, che aggiunge un contorno agli elementi toccabili, che puoi rimuovere impostando border: 0.

Se utilizzi un elemento <button> su Firefox, ti viene applicato un gradiente, che puoi rimuovere impostando background-image: none.

/* Firefox Specific CSS to remove button
differences and focus ring */
.btn {
  background-image: none;
}

.btn::-moz-focus-inner {
  border: 0;
}

Prova

Disattivazione della selezione utente in corso...

Durante la creazione dell'interfaccia utente, potrebbero verificarsi scenari in cui gli utenti possono interagire con i tuoi elementi, ma preferisci eliminare il comportamento predefinito di selezione del testo con una pressione prolungata o di trascinamento del mouse sull'interfaccia utente.

Puoi farlo con la proprietà CSS user-select, ma tieni presente che eseguire questa operazione sui contenuti può essere extremely irritante per gli utenti se vuoi selezionare il testo nell'elemento. Quindi, assicurati di utilizzarlo con cautela e con parsimonia.

/* Example: Disable selecting text on a paragraph element: */
p.disable-text-selection {
  user-select: none;
}

Implementare gesti personalizzati

Se hai un'idea per interazioni e gesti personalizzati per il tuo sito, ci sono due aspetti da tenere presente:

  1. Come supportare tutti i browser.
  2. Come mantenere alta la frequenza fotogrammi.

In questo articolo, esamineremo esattamente questi argomenti riguardanti le API che dobbiamo supportare per raggiungere tutti i browser, quindi illustreremo come utilizziamo questi eventi in modo efficiente.

A seconda dell'azione che vuoi eseguire, è probabile che l'utente interagisca con un elemento alla volta oppure in grado di interagire con più elementi contemporaneamente.

In questo articolo esamineremo due esempi, entrambi dimostrativi del supporto per tutti i browser e di come mantenere elevata la frequenza frame.

GIF di esempio del tocco sul documento

Il primo esempio consente all'utente di interagire con un elemento. In questo caso, potresti voler assegnare tutti gli eventi tocco a quell'elemento, a condizione che il gesto sia iniziato inizialmente sull'elemento stesso. Ad esempio, puoi controllare l'elemento spostando un dito fuori dall'elemento a scorrimento.

Questo è utile in quanto offre una grande flessibilità all'utente, ma impone una limitazione sul modo in cui l'utente può interagire con la UI.

GIF di esempio del tocco su un elemento

Tuttavia, se prevedi che gli utenti interagiscano con più elementi contemporaneamente (utilizzando la funzionalità multi-touch), dovresti limitare il tocco all'elemento specifico.

Questo approccio è più flessibile per gli utenti, ma complica la logica di manipolazione dell'interfaccia utente ed è meno resiliente in caso di errore dell'utente.

Aggiungi listener di eventi

In Chrome (versione 55 e successive), Internet Explorer ed Edge, PointerEvents sono l'approccio consigliato per l'implementazione di gesti personalizzati.

In altri browser, TouchEvents e MouseEvents sono gli approcci corretti.

La caratteristica straordinaria di PointerEvents è che unisce più tipi di input, compresi gli eventi mouse, tocco e penna, in un unico insieme di callback. Gli eventi da ascoltare sono pointerdown, pointermove, pointerup e pointercancel.

Gli equivalenti in altri browser sono touchstart, touchmove, touchend e touchcancel per gli eventi tocco e, se vuoi implementare lo stesso gesto per l'input del mouse, devi implementare mousedown, mousemove e mouseup.

In caso di domande sugli eventi da utilizzare, consulta questa tabella degli eventi tocco, mouse e puntatore.

L'utilizzo di questi eventi richiede la chiamata del metodo addEventListener() su un elemento DOM, insieme al nome di un evento, di una funzione di callback e di un valore booleano. Il valore booleano determina se devi rilevare l'evento prima o dopo che altri elementi hanno avuto l'opportunità di rilevare e interpretare gli eventi. (true significa che l'evento deve essere prima di altri elementi).

Ecco un esempio di ascolto per l'inizio di un'interazione.

// Check if pointer events are supported.
if (window.PointerEvent) {
  // Add Pointer Event Listener
  swipeFrontElement.addEventListener('pointerdown', this.handleGestureStart, true);
  swipeFrontElement.addEventListener('pointermove', this.handleGestureMove, true);
  swipeFrontElement.addEventListener('pointerup', this.handleGestureEnd, true);
  swipeFrontElement.addEventListener('pointercancel', this.handleGestureEnd, true);
} else {
  // Add Touch Listener
  swipeFrontElement.addEventListener('touchstart', this.handleGestureStart, true);
  swipeFrontElement.addEventListener('touchmove', this.handleGestureMove, true);
  swipeFrontElement.addEventListener('touchend', this.handleGestureEnd, true);
  swipeFrontElement.addEventListener('touchcancel', this.handleGestureEnd, true);

  // Add Mouse Listener
  swipeFrontElement.addEventListener('mousedown', this.handleGestureStart, true);
}

Prova

Gestire l'interazione con un singolo elemento

Nel breve snippet di codice riportato sopra abbiamo aggiunto solo il listener di eventi iniziale per gli eventi del mouse. Il motivo è che gli eventi del mouse vengono attivati solo quando il cursore passa sopra l'elemento a cui viene aggiunto il listener di eventi.

TouchEvents monitorerà un gesto dopo l'avvio indipendentemente dal punto in cui si verifica il tocco e PointerEvents monitorerà gli eventi a prescindere da dove avviene il tocco dopo la chiamata di setPointerCapture su un elemento DOM.

Per gli eventi di spostamento e fine del mouse, aggiungiamo i listener di eventi nel metodo di avvio del gesto e i listener al documento, il che significa che può monitorare il cursore fino al completamento del gesto.

I passaggi per implementare questa funzionalità sono:

  1. Aggiungi tutti i listener TouchEvent e PointerEvent. Per MouseEvents aggiungi solo l'evento di inizio.
  2. All'interno del callback del gesto di avvio, associa gli eventi di spostamento e fine del mouse al documento. In questo modo vengono ricevuti tutti gli eventi del mouse indipendentemente dal fatto che si verifichino o meno sull'elemento originale. Per PointerEvents dobbiamo chiamare setPointerCapture() sul nostro elemento originale per ricevere tutti gli ulteriori eventi. Quindi, gestisci l'inizio del gesto.
  3. Gestisci gli eventi di spostamento.
  4. All'evento finale, rimuovi i listener di fine e spostamento del mouse dal documento e termina il gesto.

Di seguito è riportato uno snippet del nostro metodo handleGestureStart() che aggiunge gli eventi di spostamento e fine al documento:

// Handle the start of gestures
this.handleGestureStart = function(evt) {
  evt.preventDefault();

  if(evt.touches && evt.touches.length > 1) {
    return;
  }

  // Add the move and end listeners
  if (window.PointerEvent) {
    evt.target.setPointerCapture(evt.pointerId);
  } else {
    // Add Mouse Listeners
    document.addEventListener('mousemove', this.handleGestureMove, true);
    document.addEventListener('mouseup', this.handleGestureEnd, true);
  }

  initialTouchPos = getGesturePointFromEvent(evt);

  swipeFrontElement.style.transition = 'initial';
}.bind(this);

Prova

Il callback finale che aggiungiamo è handleGestureEnd(), che rimuove i listener di eventi di spostamento e di fine dal documento e rilascia l'acquisizione del puntatore quando il gesto termina in questo modo:

// Handle end gestures
this.handleGestureEnd = function(evt) {
  evt.preventDefault();

  if (evt.touches && evt.touches.length > 0) {
    return;
  }

  rafPending = false;

  // Remove Event Listeners
  if (window.PointerEvent) {
    evt.target.releasePointerCapture(evt.pointerId);
  } else {
    // Remove Mouse Listeners
    document.removeEventListener('mousemove', this.handleGestureMove, true);
    document.removeEventListener('mouseup', this.handleGestureEnd, true);
  }

  updateSwipeRestPosition();

  initialTouchPos = null;
}.bind(this);

Prova

Seguendo questo schema di aggiunta dell'evento di spostamento al documento, se l'utente inizia a interagire con un elemento e sposta il gesto al di fuori dell'elemento, continueremo a vedere movimenti del mouse indipendentemente da dove si trova sulla pagina, perché gli eventi vengono ricevuti dal documento.

Questo diagramma mostra il comportamento degli eventi tocco quando aggiungiamo gli eventi di spostamento e fine al documento una volta iniziato un gesto.

Illustrazione di eventi touch vincolanti da documentare in
&quot;touchstart&quot;

Rispondere al tocco in modo efficiente

Ora che abbiamo gestito gli eventi di inizio e fine, possiamo effettivamente rispondere agli eventi touch.

Per qualsiasi evento di avvio e spostamento, puoi estrarre facilmente x e y da un evento.

L'esempio seguente verifica se l'evento proviene da un TouchEvent verificando se targetTouches esiste. In questo caso, estrarrà clientX e clientY dal primo tocco. Se l'evento è PointerEvent o MouseEvent, estrae clientX e clientY direttamente dall'evento stesso.

function getGesturePointFromEvent(evt) {
    var point = {};

    if (evt.targetTouches) {
      // Prefer Touch Events
      point.x = evt.targetTouches[0].clientX;
      point.y = evt.targetTouches[0].clientY;
    } else {
      // Either Mouse event or Pointer Event
      point.x = evt.clientX;
      point.y = evt.clientY;
    }

    return point;
  }

Prova

Un TouchEvent ha tre elenchi contenenti dati touch:

  • touches: elenco di tutti i tocchi attuali sullo schermo, a prescindere dall'elemento DOM in cui si trovano.
  • targetTouches: elenco di tocchi attualmente presenti nell'elemento DOM a cui è associato l'evento.
  • changedTouches: elenco di tocchi modificati dando origine all'evento.

Nella maggior parte dei casi, targetTouches ti offre tutto ciò che ti serve e vuoi. Per ulteriori informazioni su questi elenchi, vedi Elenchi touch.

Utilizzo di requestAnimationFrame

Poiché i callback degli eventi vengono attivati nel thread principale, vogliamo eseguire il minor numero possibile di codice nei callback per i nostri eventi, mantenendo alta la nostra frequenza frame e evitando il jank.

Grazie a requestAnimationFrame() abbiamo l'opportunità di aggiornare l'interfaccia utente appena prima che il browser abbia intenzione di disegnare un frame. Questo ci aiuterà a evitare le operazioni di callback degli eventi.

Se non hai dimestichezza con requestAnimationFrame(), puoi scoprire di più qui.

Una tipica implementazione consiste nel salvare le coordinate x e y dagli eventi di avvio e spostamento e di richiedere un frame di animazione all'interno del callback dell'evento di spostamento.

Nella nostra demo, memorizziamo la posizione del contatto iniziale in handleGestureStart() (cerca initialTouchPos):

// Handle the start of gestures
this.handleGestureStart = function(evt) {
  evt.preventDefault();

  if (evt.touches && evt.touches.length > 1) {
    return;
  }

  // Add the move and end listeners
  if (window.PointerEvent) {
    evt.target.setPointerCapture(evt.pointerId);
  } else {
    // Add Mouse Listeners
    document.addEventListener('mousemove', this.handleGestureMove, true);
    document.addEventListener('mouseup', this.handleGestureEnd, true);
  }

  initialTouchPos = getGesturePointFromEvent(evt);

  swipeFrontElement.style.transition = 'initial';
}.bind(this);

Il metodo handleGestureMove() memorizza la posizione del suo evento prima di richiedere un frame di animazione se necessario, passando la nostra funzione onAnimFrame() come callback:

this.handleGestureMove = function (evt) {
  evt.preventDefault();

  if (!initialTouchPos) {
    return;
  }

  lastTouchPos = getGesturePointFromEvent(evt);

  if (rafPending) {
    return;
  }

  rafPending = true;

  window.requestAnimFrame(onAnimFrame);
}.bind(this);

Il valore onAnimFrame è una funzione che, quando chiamata, modifica l'interfaccia utente per spostarlo. Passando questa funzione in requestAnimationFrame(), chiediamo al browser di chiamarla poco prima di aggiornare la pagina (ovvero di colorare eventuali modifiche alla pagina).

Nel callback handleGestureMove(), controlliamo inizialmente se rafPending è false, il che indica se onAnimFrame() è stato chiamato da requestAnimationFrame() dall'ultimo evento di spostamento. Ciò significa che abbiamo un solo requestAnimationFrame() in attesa di esecuzione alla volta.

Quando viene eseguito il callback onAnimFrame(), impostiamo la trasformazione su tutti gli elementi che vogliamo spostare prima di aggiornare rafPending in false, in modo che l'evento touch successivo richieda un nuovo frame dell'animazione.

function onAnimFrame() {
  if (!rafPending) {
    return;
  }

  var differenceInX = initialTouchPos.x - lastTouchPos.x;
  var newXTransform = (currentXPosition - differenceInX)+'px';
  var transformStyle = 'translateX('+newXTransform+')';

  swipeFrontElement.style.webkitTransform = transformStyle;
  swipeFrontElement.style.MozTransform = transformStyle;
  swipeFrontElement.style.msTransform = transformStyle;
  swipeFrontElement.style.transform = transformStyle;

  rafPending = false;
}

Controlla i gesti usando le azioni touch

La proprietà CSS touch-action consente di controllare il comportamento tocco predefinito di un elemento. Nei nostri esempi, utilizziamo touch-action: none per impedire al browser di eseguire operazioni con il tocco degli utenti, consentendoci di intercettare tutti gli eventi touch.

/* Pass all touches to javascript: */
button.custom-touch-logic {
  touch-action: none;
}

L'uso di touch-action: none è un'opzione quasi nucleare, in quanto impedisce tutti i comportamenti predefiniti del browser. In molti casi, una delle opzioni seguenti è la soluzione migliore.

touch-action ti consente di disattivare i gesti implementati da un browser. Ad esempio, IE10 e versioni successive supportano il gesto di doppio tocco per eseguire lo zoom. Se imposti un valore touch-action su manipulation, impedisci il comportamento predefinito del doppio tocco.

In questo modo puoi implementare autonomamente un gesto di doppio tocco.

Di seguito è riportato un elenco dei valori touch-action di uso comune:

Parametri azione tocco
touch-action: none Il browser non gestisce alcuna interazione touch.
touch-action: pinch-zoom Disattiva tutte le interazioni del browser, ad esempio "touch-action: none" a parte "pinch-zoom", che viene comunque gestito dal browser.
touch-action: pan-y pinch-zoom Gestisci gli scorrimenti orizzontali in JavaScript senza disattivare lo scorrimento verticale o lo zoom tramite pizzico (ad es. caroselli di immagini).
touch-action: manipulation Disattiva il gesto del doppio tocco che evita qualsiasi ritardo di clic da parte del browser. Lascia scorrere e pizzica verso l'alto lo zoom sul browser.

Supporto delle versioni precedenti di IE

Se desideri supportare IE10, dovrai gestire le versioni con prefisso del fornitore di PointerEvents.

Per verificare il supporto di PointerEvents, generalmente cercheresti window.PointerEvent, mentre in IE10 cercheresti window.navigator.msPointerEnabled.

I nomi degli eventi con prefissi dei fornitori sono: 'MSPointerDown', 'MSPointerUp' e 'MSPointerMove'.

L'esempio seguente mostra come verificare la presenza di supporto e cambiare i nomi degli eventi.

var pointerDownName = 'pointerdown';
var pointerUpName = 'pointerup';
var pointerMoveName = 'pointermove';

if (window.navigator.msPointerEnabled) {
  pointerDownName = 'MSPointerDown';
  pointerUpName = 'MSPointerUp';
  pointerMoveName = 'MSPointerMove';
}

// Simple way to check if some form of pointerevents is enabled or not
window.PointerEventsSupport = false;
if (window.PointerEvent || window.navigator.msPointerEnabled) {
  window.PointerEventsSupport = true;
}

Per ulteriori informazioni, consulta questo articolo di aggiornamento di Microsoft.

Riferimento

Pseudoclassi per gli stati touch

Classe Esempio Descrizione
:hover
Pulsante in stato premuto
da inserire quando il cursore viene posizionato su un elemento. Le modifiche nell'interfaccia utente al passaggio del mouse sono utili per incoraggiare gli utenti a interagire con gli elementi.
:focus
Pulsante con stato attivo
Inserito quando l'utente utilizza il tasto Tab per passare da un elemento all'altro di una pagina. Lo stato attivo consente all'utente di sapere con quale elemento sta interagendo e consente inoltre agli utenti di navigare facilmente nell'interfaccia utente utilizzando una tastiera.
:attivo
Pulsante in stato premuto
Inserito quando un elemento viene selezionato, ad esempio quando un utente fa clic o tocca un elemento.

Il riferimento definitivo agli eventi tocco è disponibile qui: W3C Touch Eventi.

Eventi di tocco, mouse e puntatore

Questi eventi sono i componenti di base per l'aggiunta di nuovi gesti alla tua applicazione:

Eventi tocco, mouse, puntatore
touchstart, mousedown e pointerdown Questo avviene quando un dito tocca per la prima volta un elemento o quando l'utente fa clic sul mouse.
touchmove, mousemove e pointermove Questo avviene quando l'utente sposta il dito sullo schermo o trascina con il mouse.
touchend, mouseup e pointerup Questo avviene quando l'utente solleva il dito dallo schermo o rilascia il mouse.
touchcancel pointercancel Questa chiamata viene richiamata quando il browser annulla i gesti tattili. Ad esempio, un utente tocca un'app web e poi cambia scheda.

Elenchi touch

Ogni evento tocco include tre attributi dell'elenco:

Attributi evento tocco
touches Elenco di tutti i tocchi attuali sullo schermo, indipendentemente dagli elementi toccati.
targetTouches Elenco di tocchi iniziati sull'elemento target dell'evento attuale. Ad esempio, se ti associ a un <button>, al momento riceverai i tocchi soltanto su quel pulsante. Se associ al documento, riceverai tutti i tocchi attualmente sul documento.
changedTouches Elenco di tocchi che sono stati modificati e che hanno portato all'attivazione dell'evento:
  • Per l'evento touchstart: elenco dei touchpoint appena attivati con l'evento corrente.
  • Per l'evento touchmove: elenco dei punti di contatto che sono stati spostati dall'ultimo evento.
  • Per gli eventi touchend e touchcancel, elenco dei punti di contatto appena rimossi dalla superficie.

Abilitazione del supporto dello stato attivo su iOS

Purtroppo Safari su iOS non applica lo stato attivo per impostazione predefinita. Per farlo funzionare devi aggiungere un listener di eventi touchstart al corpo del documento o a ogni elemento.

Dovresti eseguire questa operazione dietro un test dello user agent, in modo che sia eseguita solo su dispositivi iOS.

L'aggiunta di un tocco iniziale al corpo ha il vantaggio di essere applicata a tutti gli elementi nel DOM, ma potrebbero verificarsi problemi di prestazioni durante lo scorrimento della pagina.

window.onload = function() {
  if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
    document.body.addEventListener('touchstart', function() {}, false);
  }
};

L'alternativa è aggiungere i listener touch start a tutti gli elementi con cui è possibile interagire nella pagina, riducendo alcuni dei problemi di prestazioni.

window.onload = function() {
  if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
    var elements = document.querySelectorAll('button');
    var emptyFunction = function() {};

    for (var i = 0; i < elements.length; i++) {
        elements[i].addEventListener('touchstart', emptyFunction, false);
    }
  }
};