Aggiungi il tocco al tuo sito

I touchscreen sono disponibili su sempre più dispositivi, dagli smartphone agli schermi dei computer. La tua app dovrebbe rispondere al tocco in modi intuitivi e belli.

Matt Gaunt

I touchscreen sono disponibili su sempre più dispositivi, dagli smartphone agli schermi dei computer. Quando gli utenti scelgono di interagire con la tua UI, la tua app deve rispondere al tocco in modo intuitivo.

Rispondere agli stati degli elementi

Hai mai toccato o fatto clic su un elemento in una pagina web e ti sei chiesto se il sito lo avesse effettivamente rilevato?

Modificare semplicemente il colore di un elemento quando gli utenti toccano o interagiscono con parti della tua UI offre una rassicurante conferma del funzionamento del sito. Non solo attenua la frustrazione, ma può anche dare un'esperienza rapida e reattiva.

Gli elementi DOM possono ereditare uno dei seguenti stati: predefinito, attivo, con il mouse sopra e attivo. Per modificare l'interfaccia utente 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 mostra colori diversi per
gli stati dei pulsanti

Nella maggior parte dei browser per dispositivi mobili, gli stati relativi a passaggio del mouse e/o stato attivo vengono applicati a un elemento dopo che è stato toccato.

Valuta attentamente gli stili che imposti e il loro aspetto per l'utente al termine del tocco.

Eliminazione degli stili del browser predefiniti

Dopo aver aggiunto gli stili per i diversi stati, noterai che la maggior parte dei browser implementa i propri stili in risposta al tocco di un utente. Ciò è dovuto principalmente al momento del primo lancio dei dispositivi mobili, diversi siti non avevano lo stile per lo stato :active. Di conseguenza, molti browser hanno aggiunto ulteriori colori o stili di evidenziazione per fornire un feedback agli utenti.

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

.btn:focus {
    outline: 0;

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

Safari e Chrome aggiungono un colore di evidenziazione dei tocchi 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, può essere rimossa impostando border: 0.

Se utilizzi un elemento <button> su Firefox, 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 dell'utente

Quando crei l'interfaccia utente, potrebbero verificarsi scenari in cui vuoi che gli utenti interagiscano con i tuoi elementi, ma vuoi eliminare il comportamento predefinito di selezione del testo con una pressione prolungata o trascinando il mouse sull'interfaccia utente.

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

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

Implementare gesti personalizzati

Se hai un'idea per gesti e interazioni personalizzati per il tuo sito, tieni presente due argomenti:

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

In questo articolo esamineremo proprio questi argomenti, che riguardano le API che dobbiamo supportare per raggiungere tutti i browser, e poi spiegheremo come utilizzare questi eventi in modo efficiente.

A seconda di cosa vuoi che faccia il gesto, probabilmente vorrai che l'utente interagisca con un elemento alla volta o che possa interagire con più elementi contemporaneamente.

In questo articolo esamineremo due esempi, entrambi che dimostrano il supporto per tutti i browser e come mantenere elevata la frequenza fotogrammi.

GIF di esempio di 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'unico elemento, a condizione che il gesto sia iniziato inizialmente sull'elemento stesso. Ad esempio, puoi controllare un elemento scorrendolo con un dito.

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

GIF di esempio di tocco sull&#39;elemento

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

Questo è più flessibile per gli utenti, ma complica la logica per la manipolazione dell'interfaccia utente ed è meno resiliente agli errori dell'utente.

Aggiungere listener di eventi

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

In altri browser, TouchEvents e MouseEvents sono l'approccio corretto.

La grande funzionalità di PointerEvents è che unisce più tipi di input, tra cui eventi del 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.

Se hai domande su quali eventi utilizzare, consulta questa tabella degli eventi tocco, mouse e cursore.

L'utilizzo di questi eventi richiede la chiamata del metodo addEventListener() su un elemento DOM, insieme al nome di un evento, una funzione di callback e 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 indica che vuoi che l'evento venga visualizzato prima di altri elementi).

Ecco un esempio di ascolto dell'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 è aggiunto il listener di eventi.

TouchEvents monitora un gesto dopo l'avvio, indipendentemente da dove avviene il tocco, mentre PointerEvents monitora gli eventi indipendentemente da dove avviene il tocco dopo aver chiamato setPointerCapture su un elemento DOM.

Per gli eventi di movimento e fine del mouse, aggiungiamo i listener di eventi nel metodo di inizio del gesto e aggiungiamo 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 gli ascoltatori TouchEvent e PointerEvent. Per gli eventi MouseEvent, aggiungi solo l'evento di inizio.
  2. All'interno del callback del gesto di inizio, associa gli eventi di movimento del mouse e di fine al documento. In questo modo, tutti gli eventi del mouse vengono ricevuti indipendentemente dal fatto che si verifichino o meno nell'elemento originale. Per PointerEvents dobbiamo chiamare setPointerCapture() sull'elemento originale per ricevere tutti gli altri eventi. Quindi, gestisci l'inizio del gesto.
  3. Gestisci gli eventi di spostamento.
  4. Nell'evento di fine, rimuovi dal documento i listener di movimento del mouse e di fine 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 di fine che aggiungiamo è handleGestureEnd(), che rimuove i listener di eventi di movimento e fine dal documento e rilascia il rilevamento del cursore al termine del gesto nel seguente 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 ricevere i movimenti del mouse indipendentemente da dove si trovano nella pagina, perché gli eventi vengono ricevuti dal documento.

Questo diagramma mostra lo stato degli eventi touch quando aggiungiamo gli eventi di spostamento e fine al documento dopo l'inizio di un gesto.

Illustrazione dell&#39;associazione degli eventi touch al documento in
`touchstart`

Rispondere al tocco in modo efficiente

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

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

Il seguente esempio verifica se l'evento proviene da un TouchEvent controllando se targetTouches esiste. In questo caso, estrae clientX e clientY dal primo tocco. Se l'evento è un PointerEvent o un 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 contiene tre elenchi contenenti dati tocco:

  • touches: elenco di tutti i tocchi attuali sullo schermo, a prescindere dall'elemento DOM su cui sono attivati.
  • targetTouches: elenco dei tocchi attualmente sull'elemento DOM a cui è associato l'evento.
  • changedTouches: elenco dei tocchi che sono cambiati e che hanno attivato l'evento.

Nella maggior parte dei casi, targetTouches ti offre tutto ciò di cui hai bisogno e che ti serve. Per maggiori informazioni su questi elenchi, consulta Elenchi di tocco.

Utilizzare requestAnimationFrame

Poiché i callback eventi vengono attivati nel thread principale, vogliamo eseguire il meno codice possibile nei callback per i nostri eventi, mantenendo alta la frequenza frame ed evitando il jitter.

Con requestAnimationFrame() abbiamo l'opportunità di aggiornare la UI poco prima che il browser intenda tracciare un frame e ci aiuterà a rimuovere un po' di lavoro dai callback eventi.

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

Un'implementazione tipica consiste nel salvare le coordinate x e y degli eventi start e move e richiedere un frame di animazione all'interno del callback dell'evento move.

Nella nostra demo, memorizziamo la posizione del tocco 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 relativo evento prima di richiedere un frame di animazione, se necessario, passando la 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, se chiamata, modifica la nostra UI per spostarla. Passando questa funzione a requestAnimationFrame(), diciamo al browser di chiamarla appena prima di aggiornare la pagina (ovvero di applicare eventuali modifiche alla pagina).

Nel callback handleGestureMove() inizialmente controlliamo se rafPending è falso, il che indica se onAnimFrame() è stato chiamato da requestAnimationFrame() dall'ultimo evento di spostamento. Ciò significa che abbiamo un solo requestAnimationFrame() in attesa di essere eseguito in un determinato momento.

Quando viene eseguito il nostro callback onAnimFrame(), impostiamo la trasformazione su tutti gli elementi che vogliamo spostare prima di aggiornare rafPending in false, consentendo all'evento tocco successivo di richiedere un nuovo frame di 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;
}

Controllare i gesti con le azioni tocco

La proprietà CSS touch-action consente di controllare il comportamento predefinito del tocco di un elemento. Nei nostri esempi, utilizziamo touch-action: none per impedire al browser di fare qualsiasi cosa con il tocco di un utente, il che ci consente di intercettare tutti gli eventi tocco.

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

L'utilizzo di touch-action: none è un po' un'opzione drastica, in quanto impedisce tutti i comportamenti predefiniti del browser. In molti casi, una delle opzioni riportate di seguito è una soluzione migliore.

touch-action ti consente di disattivare i gesti implementati da un browser. Ad esempio, IE10 e versioni successive supportano il gesto di tocco doppio per lo zoom. Se imposti touch-action di 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 più utilizzati:

Parametri azione tocco
touch-action: none Il browser non gestirà alcuna interazione tocco.
touch-action: pinch-zoom Disattiva tutte le interazioni del browser come "touch-action: none", ad eccezione di "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 con due dita (ad es. caroselli di immagini).
touch-action: manipulation Disattiva il gesto di doppio tocco, evitando così qualsiasi ritardo nel clic del browser. Lascia lo scorrimento e lo zoom con pizzico al browser.

Supporto di versioni precedenti di IE

Se vuoi supportare IE10, devi gestire le versioni con prefisso del fornitore diPointerEvents.

Per verificare il supporto di PointerEvents, in genere cerchi window.PointerEvent, ma in IE10 cerchi window.navigator.msPointerEnabled.

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

L'esempio seguente mostra come verificare l'assistenza 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 sugli aggiornamenti di Microsoft.

Riferimento

Pseudoclassi per stati del tocco

Classe Esempio Descrizione
:hover
Pulsante in stato premuto
Inserito quando un cursore viene posizionato su un elemento. Le modifiche all'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 scorre tra gli elementi di una pagina. Lo stato attivo consente all'utente di sapere con quale elemento sta attualmente interagendo e di navigare facilmente nella UI utilizzando una tastiera.
:active
Pulsante in stato premuto
Inserito quando un elemento viene selezionato, ad esempio quando un utente fa clic o tocca un elemento.

Puoi trovare la documentazione definitiva sugli eventi touch qui: W3C Touch Events.

Eventi touch, mouse e del cursore

Questi eventi sono gli elementi costitutivi per aggiungere nuovi gesti alla tua applicazione:

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

Elenchi tocco

Ogni evento tocco include tre attributi dell'elenco:

Attributi evento touch
touches Elenco di tutti i tocchi attuali sullo schermo, a prescindere dagli elementi toccati.
targetTouches Elenco di tocchi iniziati sull'elemento target dell'evento corrente. Ad esempio, se esegui l'associazione a un elemento <button>, riceverai solo i tocchi relativi a quel pulsante. Se esegui il binding al documento, riceverai tutti i tocchi attualmente presenti nel documento.
changedTouches Elenco dei tocchi che sono cambiati con l'attivazione dell'evento:
  • Per l'evento touchstart : elenco dei touchpoint che sono diventati attivi con l'evento corrente.
  • Per l'evento touchmove: elenco dei touchpoint che si sono spostati dall'ultimo evento.
  • Per gli eventi touchend e touchcancel : elenco dei punti di contatto appena rimossi dalla superficie.

Attivazione del supporto dello stato attivo su iOS

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

Dovresti eseguire questa operazione dietro un test dell'agente utente in modo che venga eseguito solo sui dispositivi iOS.

L'aggiunta di un tocco all'inizio del corpo ha il vantaggio di essere applicato a tutti gli elementi nel DOM, ma potrebbero verificarsi problemi di prestazioni quando si scorre la pagina.

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

L'alternativa è aggiungere gli ascoltatori di inizio tocco a tutti gli elementi interattivi della pagina, alleviando 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);
    }
  }
};