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.
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;
}
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;
}
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;
}
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:
- Come supportare tutti i browser.
- 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.
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.
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);
}
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:
- Aggiungi tutti gli ascoltatori TouchEvent e PointerEvent. Per gli eventi MouseEvent, aggiungi solo l'evento di inizio.
- 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. - Gestisci gli eventi di spostamento.
- 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);
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);
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.
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;
}
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:
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
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:
Elenchi tocco
Ogni evento tocco include tre attributi dell'elenco:
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);
}
}
};