Il touch è una esperienza d'uso dei dispositivi che ha modificato il nostro modo di comportarci davanti agli schermi. Questo fenomeno si somma alla continua evoluzione di HTML5 con le tante innovazioni che esso porta nel Web: dal supporto integrato ai file multimediali al Canvas, alla geolocalizzazione e altro ancora.
In questo scenario nascono nuovi modi di creare e fruire il Web e aziende come Microsoft non stanno a guardare. Redmond sembra avere una visione piuttosto precisa del futuro e per il Windows Store si possono sviluppare applicazioni e giochi destinati a desktop e mobile, anche sfruttando HTML5 e JavaScript.
Inoltre Microsoft punta molto all'innovazione touch collaborando con il W3C alla definizione della specifica Pointer Events, una API standard per la gestione degli input con diversi dispositivi di puntamento (mouse, tocco con dita e penne)
Internet Explorer (10 e successive) integra il supporto a questa API (ancora in fase di definizione) della quale analizzeremo alcuni esempi per illustrare come è stata impiegata nella realizzazione del sito ExploreTouch.ie e vedere in che modo può permettere la creazione di interfacce ricche, intuitive e divertenti, semplificandone drasticamente lo sviluppo e, quindi, il codice da scrivere.
Come è stato realizzato ExploreTouch.ie
Il sito ExploreTouch.ie, prodotto da Fantasy Interactive in collaborazione con Microsoft, permette all'utente di personalizzare la miscelazione del brano "Your Touch" di Blake Lewis (cantante, compositore e DJ) interagendo con piccole sfere che rappresentano le sue componenti fondamentali (percussioni, basso, tastiere, voce, ecc.), toccandole e trascinandole all'interno di una sfera centrale per riprodurne il suono.
Oltre alla demo vera e propria, il sito presenta un menu da cui è possibile accedere al tutorial che spiega nel dettaglio come utilizzarlo e condividerlo con altre persone.
La demo interattiva risponde a un'ampia gamma di azioni che si possono eseguire tramite il tocco con una o più dita: swipe (strisciare), tap and hold (tocca e trascina), scratch (graffio), pinch (pizzico) e rotazione; queste operazioni sono fruibili anche con il mouse, grazie alla compatibilità fornita dall'API Pointer Events, ma per una esperienza completa si consiglia di sperimentare il sito su un dispositivo "multi touch" equipaggiato con Windows 8 e Internet Explorer versione 10 o superiore. Vediamo ora come sono stati gestiti alcuni degli eventi basilari.
Per una infarinatura sui rudimenti dell'API Pointer Events è possibile fare riferimento a questo articolo introduttivo.
La struttura di base
Qui di seguito è riportato il markup e il codice sorgente della struttura base di una pagina predisposta a interagire con i Pointer Events:
<!doctype html>
<html>
<head>
<title>Gesture detection example</title>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<style>
.gestureSurface {
width: 640px; height: 480px;
background-color: red;
-ms-touch-action: none;
}
</style>
</head>
<body>
<div class="gestureSurface"></div>
<script>
// Ricerca l’elemento che rappresenta la superficie di interazione
var surface = document.querySelector('.gestureSurface');
// Inizializza l’oggetto MSGesture
var msGesture = new MSGesture();
// Imposta il target degli eventi
msGesture.target = surface;
</script>
</body>
</html>
Il codice ricalca concettualmente quello scritto per "ExploreTouch", ma privo delle parti che al momento non interessano e appositamente pensato per introdurre i concetti che stiamo per affrontare.
Analizziamo singolarmente gli elementi che sono stati aggiunti al markup di base della pagina HTML5 per costruire questo scheletro con cui è possibile iniziare a lavorare.
Innanzitutto, è necessario inserire un elemento <div> che rappresenta la nostra "superficie", ossia l'area della pagina Web su cui verranno disegnati o inseriti gli oggetti dell'interfaccia con cui l'utente può interagire:
<div class="gestureSurface"></div>
Attraverso la classe "gestureSurface" assegnata al <div>, possiamo associare uno stile CSS per definire le caratteristiche del riquadro:
<style>
.gestureSurface {
width: 640px; height: 480px;
background-color: red;
-ms-touch-action: none;
}
</style>
Da notare la presenza dell'attributo -ms-touch-action che, impostato a "none
", inibisce le operazioni di zoom e pinch gestite automaticamente dal browser per le pagine Web convenzionali, affinché sia possibile controllare programmaticamente tali azioni tramite JavaScript e l'API.
Va precisato che in futuro, nella fase di finalizzazione dello standard, il prefisso "-ms-
" (specifico del vendor Microsoft) verrà probabilmente rimosso dall'attributo che assumerà la forma più generalizzata touch-action
; questa modifica interesserà tutti gli attributi che fanno parte dell'API Pointer Events.
Per garantire l'attivazione del supporto agli eventi di tocco, e per inibire qualsiasi opzione di "Visualizzazione compatibilità" di Internet Explorer, occorre segnalare al browser che il nostro sito è compatibile con le versioni più recenti di IE inserendo l'apposito tag <meta>
:
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
Dal punto di vista del markup di base, la pagina è completa. Aggiungiamo ora il codice JavaScript che ci consente di individuare l'elemento interessato dagli eventi (il nostro <div>
) e passarlo al "motore" dell'API affinché possa monitorarlo:
<script>
// Ricerca l'elemento che rappresenta la superficie di interazione
var surface = document.querySelector('.gestureSurface');
// Inizializza l'oggetto MSGesture
var msGesture = new MSGesture();
// Imposta il controllo target su cui gestire gli eventi
msGesture.target = surface;
</script>
Gestire le azioni dell'utente
Ora che il sistema è stato inizializzato e che abbiamo definito l'elemento su cui possiamo interagire, dobbiamo "agganciare" specifiche funzioni di callback ad alcuni degli eventi globali che l'API ci consente di gestire:
// Associa la funzione per rispondere all'evento di tocco (o clic)
surface.addEventListener('MSPointerDown', function(event) {
// Registra il puntatore
msGesture.addPointer(event.pointerId);
});
// Associa la funzione di reset all'inizio di una gesture dell'utente
surface.addEventListener('MSGestureStart', resetGesture);
// Associa la funzione da invocare quando cambiano i parametri della gesture
surface.addEventListener('MSGestureChange', detectGesture);
L'evento MSPointerDown consente di intercettare il tocco (tap) dell'utente, oppure il clic sul tasto sinistro del mouse, o l'appoggio della penna sulla superficie del dispositivo, azioni che danno luogo alla creazione di un pointer (puntatore virtuale), che rappresenta essenzialmente un punto di contatto del device di input con il dispositivo.
Nell'esempio, "resetGesture
" e "detectGesture
" sono funzioni di callback personalizzate per la gestione degli eventi ricevuti dall'API in risposta ad azioni dell'utente; vediamo come sono state implementate:
// Definisce le costanti
var MIN_ROTATION = 20; // Variazione minima della rotazione
var MIN_SCALE = .3; // Variazione minima di scala per pinch/zoom
var MIN_SWIPE_DISTANCE = 50; // Distanza minima da percorrere per lo swipe
// Definisce le variabili di stato e controllo delle gesture
var rotation, scale, recognized, translationX, translationY;
/**
* Reset gesture
*/
function resetGesture(event) {
// Ripristina i valori per la gestione di una nuova gesture
rotation = 0;
scale = 1;
translationX = translationY = 0;
recognized = false;
}
/**
* Detect gesture
*/
function detectGesture(event) {
// Ignora gli spostamenti dovuti all'inerzia
if (event.detail == event.MSGESTURE_FLAG_INERTIA) {
return;
}
// Aggiorna i valori che tracciano il movimento
rotation += event.rotation * 180 / Math.PI;
scale *= event.scale;
translationX += event.translationX;
translationY += event.translationY;
// Se non è in corso una gesture, tenta di riconoscerne una nuova
if (!recognized) {
// Memorizza la direzione del movimento
var direction;
// Verifica se è in corso uno swipe in senso orizzontale
// controllando la distanza percorsa dal puntatore
if (Math.abs(translationX) > MIN_SWIPE_DISTANCE) {
// Imposta il flag che indica il riconoscimento di una gesture
recognized = true;
// Verifica la direzione dello spostamento
direction = translationX < 0 ? 'left' : 'right';
// Verifica se è in corso uno swipe in senso verticale
} else if (Math.abs(translationY) > MIN_SWIPE_DISTANCE) {
// Imposta il flag che indica il riconoscimento di una gesture
recognized = true;
// Verifica la direzione dello spostamento
direction = translationY < 0 ? 'up' : 'down'
}
// Controlla se è in corso una rotazione
if (Math.abs(rotation) >= MIN_ROTATION) {
// Imposta il flag che indica il riconoscimento di una gesture
recognized = true;
// Controlla se è in corso un'azione di pinch/zoom
} else if (Math.abs(scale - 1) > MIN_SCALE) {
// Imposta il flag che indica il riconoscimento di una gesture
recognized = true;
}
}
}
Lo scopo di ciascuna istruzione dell'esempio è documentato nei commenti presenti nel codice sorgente.
In breve, ciò che abbiamo fatto è definire il corpo delle funzioni di callback che sono richiamate dall'API quando l'utente agisce in qualche modo sul "pointer" variandone lo stato (ad esempio, quando varia lo spostamento orizzontale/verticale del dito, la scala su "pizzico" o zoom, la rotazione, ecc.); quando i cambiamenti superano le soglie di tolleranza che abbiamo definito in modo arbitrario tramite costanti, la gesture specifica che ci interessa viene "riconosciuta" e possiamo quindi andare ad eseguire tutte le operazioni connesse al tipo di interazione e all'entità delle variazioni (il codice di questa parte è stato omesso per brevità, ma può essere analizzato sul sito "ExploreTouch").
È interessante notare alcune peculiarità dell'API Pointer Events, ossia
- la capacità di fornire agli eventi informazioni generalmente complesse da determinare per lo sviluppatore, ad esempio il "delta" già calcolato delle distanze di spostamento, della rotazione e così via, e non ci si deve necessariamente accontentare di valori assoluti e grezzi;
- il supporto di funzionalità avanzate quali la gestione dell'inerzia, cioè la prosecuzione del movimento avviato dal tocco dell'utente quando solleva il dito dalla superficie; nel nostro caso abbiamo deliberatamente intercettato questo tipo di interazione per escluderlo, quindi di fatto l'oggetto arresterà il suo spostamento non appena l'utente solleva il dito;
- la semplicità con cui è possibile inizializzare il sistema e intercettare gli eventi che interessano uno o più puntatori, qualora sia in uso un dispositivo "multi touch".
Aggiungere grafica e suono
Nella realizzazione del sito "ExploreTouch", oltre alla gestione di 8 stream audio separati per la composizione musicale, particolare attenzione è stata data all'aspetto grafico, aggiungendo bagliori, particelle e altri "effetti speciali" al passaggio del dito (o del mouse) che l'applicazione è in grado di riprodurre in modo estremamente fluido grazie a HTML5 e all'accelerazione hardware ormai sfruttabile da qualsiasi browser moderno.
Disegno degli elementi grafici
Per il disegno degli elementi grafici si fa generalmente ricorso all'oggetto Canvas di HTML5, che rappresenta una sorta di tela (trasparente) su cui possiamo andare a tracciare forme geometriche e immagini.
Vediamo una porzione di codice sorgente, estratta dal sito e opportunamente semplificata, che mostra come disegnare il "bulbo" luminescente e gestire la creazione di un buffer "off screen" (fuori schermo, non visibile) per ottimizzare le performance di resa grafica. La spiegazione delle principali istruzioni la trovate nei commenti presenti nel codice.
var buffers = [];
drawLightBlob(ctx, 255, 0, 0, 300, 300, 100);
/**
* Disegna un bagliore di luce (con buffering offscreen)
*/
function drawLightBlob(ctx, r, g, b, x, y, radius, colorString) {
// Definisce le variabili di lavoro
var buffer, bufferCanvas, gradient, id, colorStop;
// Crea un ID univoco per il bulbo del colore specifico
id = colorString.split(',').join('');
// Verifica se è stato creato un buffer per questo bulbo
if (this.buffers[id] != null) {
// Un buffer esiste per il bulbo di questo colore, quindi lo utilizza
bufferCanvas = this.buffers[id];
} else {
// Il buffer non è stato trovato, quindi è necessario crearlo
bufferCanvas = document.createElement('canvas');
bufferCanvas.width = radius * 2;
bufferCanvas.height = radius * 2;
buffer = bufferCanvas.getContext('2d');
// Crea il gradiente radiale e disegna il cerchio per creare il bulbo
buffer.beginPath();
gradient = buffer.createRadialGradient(radius, radius, 0, radius, radius, radius);
gradient.addColorStop(0, "rgba(255, 255, 255, 1)");
colorStop = "rgba(" + r + ", " + g + ", " + b + ", 0)";
gradient.addColorStop(0.7, colorStop);
buffer.fillStyle = gradient;
buffer.arc(radius, radius, radius, Math.PI * 2, false);
buffer.closePath();
buffer.fill();
// Salva il buffer per eventuali riutilizzi successivi
this.buffers[id] = bufferCanvas;
}
// Definisce la modalità con cui gestire la sovrapposizione
ctx.globalCompositeOperation = 'lighter';
// Disegna il bulbo nella posizione indicata
ctx.drawImage(bufferCanvas, x - radius, y - radius);
}
Oltre al tracciamento "manuale" delle forme e dei riempimenti colorati con l'oggetto Canvas, si può ricorrere all'uso di immagini vettoriali in formato SVG, su cui è possibile intervenire tramite JavaScript per modificarne gli attributi (dimensioni, opacità, ecc.): grazie all'accelerazione hardware, Internet Explorer supporta egregiamente entrambe le soluzioni, senza differenze significative in termini di prestazioni. Quando scegliere l'una o l'altra è una problematica che si può approfondire in questo articolo.
Riproduzione di musica e suoni
Per la riproduzione delle diverse tracce sonore che vanno a comporre il brano sono stati adottati accorgimenti particolari, poiché la loro sincronizzazione è fondamentale.
Per poter avviare il playback di tutte le tracce nello stesso momento, senza sfasamenti dovuti alle diverse tempistiche di caricamento, i file audio vengono scaricati e avviati quando il download di tutte le parti è stato completato; siccome l'inizializzazione e l'avvio del suono può richiedere tempistiche diverse quando ne viene richiesta la riproduzione via JavaScript, la posizione viene ripristinata all'inizio di ciascuna traccia quando il demo è pronto per l'uso.
In questo contesto, vale la pena ricordare che nella realizzazione di soluzioni basate sull'API Pointer Events, o su altri framework, nulla vieta di integrare librerie esterne magari per godere di un supporto particolare o avanzato nella gestione di una componente specifica del progetto; ad esempio, per semplificare la gestione della riproduzione di musica e suoni in modo uniforme su browser, dispositivi e piattaforme differenti, si può adottare una libreria come SoundManager2 e utilizzarne le funzioni all'interno degli eventi di risposta alle interazioni dell'utente.