Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

HTML5 Device Orientation API e WebSocket

Due tecnologie molto diverse tra loro che possiamo utilizzare, insieme ai CSS3D, per realizzare effetti interessanti su dispositivi mobili
Due tecnologie molto diverse tra loro che possiamo utilizzare, insieme ai CSS3D, per realizzare effetti interessanti su dispositivi mobili
Link copiato negli appunti

Come possono due tecnologie tanto diverse finire nello stesso articolo? Nelle prossime righe scopriremo alcuni spunti interessanti di cooperazione tra queste due a prima vista così distanti features introdotte dall'HTML5. Ma prima una veloce panoramica: stiamo ovviamente parlando di sviluppo orientato a device mobile, nella fattispecie le peculiarità delle API qui trattate rendono il tutto abbastanza sperimentale e, di conseguenza, ne limitano il supporto.

Stando al comodissimo caniuse i Device Orientation Event sono disponibili su iOs dalla versione 4.2 e su Android dalla 3.0, mentre i WebSocket sono ad oggi appannaggio solo del sistema operativo mobile di casa Apple. Se a questo aggiungiamo che durante questo articolo ci avvarremo anche dei CSS3D riduciamo ulteriormente il supporto ai soli device iOS.

Ovviamente la speranza è che in un prossimo futuro anche altri browser includano queste interessanti features, ma per il momento dobbiamo accontentarci del carattere limitato dell'iniziativa.

Cosa sono le Device Orientation API?

Fatte le dovute premesse cominciamo esplorando le Device Orientation API. Attraverso l'evento deviceorientation, possiamo accedere ad alcune informazioni legate alla posizione spaziale del nostro device, il tutto è contenuto in 3 variabili agganciate all'oggetto event passato al listener:

  • alpha: contiene informazioni sull'orientamento del device, è comparabile al valore di una bussola, espresso in gradi (0 - 360) con lo 0 al device che punta a sud;
  • beta: pubblica invece informazioni sull'inclinazione del device rispetto ad una posizione parallela al piano terrestre. Anche questo valore è espresso in gradi con 0 al device in piano e +/- 90 alle due verticalità;
  • gamma: esprime l'inclinazione del device rispetto all'asse che lo attraversa per il lato più lungo, anche qui lo 0 viene assegnato al device parallelo al piano terrestre e con il display verso l'alto
    mentre il valore varia da -180 a +180 gradi.

Il sistema di misurazione è simile a quello utilizzato per definire la posizione degli aerei, dove alpha corrisponde all'imbardata, beta al beccheggio e gamma al rollio. Ecco un semplicissimo esempio nell'uso di questa tecnologia, costruiamo una pagina web con il seguente codice sorgente:

<!doctype html>
<html>
<head>
<meta charset="utf8">
<title>Level One</title>
<script>
window.addEventListener ("deviceorientation", traccia, false);
function traccia(evento){
  document.querySelector('div.alpha span').innerHTML =
    Math.round(evento.alpha);
  document.querySelector('div.beta span').innerHTML =
    Math.round(evento.beta);
  document.querySelector('div.gamma span').innerHTML =
    Math.round(evento.gamma);
}
</script>
<style>
html,body{ height: 100%; }
body{
  display: box;
  box-align: center;
  box-pack: center;
  font-size: 5em;
}
</style>
<!-- scaricalo da http://leaverou.github.com/prefixfree/ -->
<script src="../prefixfree.js"></script>
</head>
<body>
<div>
  <div class="alpha">Alpha<span></span></div>
  <div class="beta" >Beta<span></span></div>
  <div class="gamma">Gamma<span></span></div>
</div>
</body>
</html>

Provando il codice appena sviluppato in un browser mobile dotato di supporto alle Device Orientation API potremo osservare dal vivo il comportamento di questi tre valori.

Ruotiamo le immagini

Anche solo focalizzandoci su di una sola variabile, alpha, possiamo ottenere risultati di tutto rispetto. Ad esempio possiamo utilizzare la potenza dei CSS3 per applicare una rotazione ad una immagine opposta alla rotazione del device dell'utente.

Per fare questo dobbiamo avvalerci di una proprietà chiamata transform: rotateY(deg), che, come il nome suggerisce, applica una rotazione pari al valore di deg sull'asse delle y che attraversa il centro dell'elemento.

Ecco come possiamo agganciare l'evento deviceorientation alla rotazione sulla nostra immagine:

window.addEventListener ("deviceorientation", traccia, false);
function traccia(evento) {
  document.querySelector('img').
  setAttribute('style','-webkit-transform: scale(4) rotateY(' + (360 - evento.alpha) + 'deg);' +
               'transform: scale(4) rotateY(' + (360 - evento.alpha) + 'deg);');
}

Notiamo come al momento non ci preoccupiamo di aggiungere prefissi sperimentali se non quello dedicato Webkit, layout engine che sottende sia il browser di casa Apple che quello di casa Google. Quando si estenderà il supporto per le features in oggetto dovremo ricordarci di aggiungere anche i prefissi sperimentali per eventuali nuovi browsers.

Prima di poter apprezzare il risultato di questo nostro esperimento dobbiamo necessariamente affrontare un altro tema; con le istruzioni fin qui svolte l'immagine ruota in opposizione alla rotazione del device, ma su se stessa e non attorno alla telecamera virtuale dalla quale l'utente sta visualizzando la pagina. Nessun problema, grazie alla proprietà transform-origin possiamo cambiare l'origine della rotazione facendola coincidere con il punto di osservazione dell'utente, ecco quindi il listato HTML di questo nostro secondo esperimento:

<!doctype html>
<html>
<head>
<meta charset="utf8">
<title>Level Three</title>
<script>
window.addEventListener ("deviceorientation", traccia, false);
function traccia(evento){
  document.querySelector('img').
  setAttribute('style',
               '-webkit-transform: scale(4) rotateY(' + (360 - evento.alpha) + 'deg);' +
               'transform: scale(4) rotateY(' + (360 - evento.alpha) + 'deg);'
  );
}
</script>
<style>
html,body { height: 100%; }
body {
  display: box;
  box-align: center;
  box-pack: center;
  perspective: 400px;
  background-image: radial-gradient(center center, white, gray);
}
img {
  display: block;
  transform-style: preserve-3d;
  transform-origin: 225px 150px 400px;
}
</style>
<!-- scaricalo da http://leaverou.github.com/prefixfree/ -->
<script src="../prefixfree.js"></script>
</head>
<body>
<img src="img/panorama.jpg">
</body>
</html>

Notiamo come la componente z della proprietà perspective-origin sia uguale alla distanza imposta dalla direttiva prospective; in questo modo la rotazione avverrà attorno al punto di osservazione dell'utente.

Ecco uno screenshot della pagina in azione:

Figura 1. (clic per ingrandire)


Creare un cubo con i CSS3D

Utilizzando in modo avanzato le proprietà di rotazione e traslazione nello spazio tridimensionale possiamo costruire un cubo: cominciamo col definire la sua struttura HTML composta dai sei div dedicati alle facce e dal div contenitore:

<!doctype html>
<html>
<head>
<meta charset="utf8">
<title>Un cubo</title>
</head>
<body>
<div id="container">
<div class="square bottom"></div>
<div class="square left"></div>
<div class="square right"></div>
<div class="square front"></div>
<div class="square top"></div>
<div class="square back"></div>
</div>
</body>
</html>

Segue la definizione del foglio di stile, per prima cose impostiamo alcune proprietà base, centrando il contenuto rispetto ai due assi, definendo la profondità prospettica ed abilitando la modalità di visualizzazione in 3D:

html,body{
  height: 100%;
}
body{
  display: box;
  box-align: center;
  box-pack: center;
  perspective: 800px;
  background-image: radial-gradient(center center, white, gray);
}
div#container{
  position: relative;
  width: 800px;
  height: 800px;
  transform-style: preserve-3d;
  transform: translateZ(-800px) rotateY(0deg);
}

Quindi impostiamo la classe .square, comune a tutte le facce del cubo:

div.square {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 800px;
  height: 800px;
}

Ora dobbiamo applicare una trasformazione diversa ad ogni singola faccia, considerando che il centro della trasformazione corrisponde al centro di ogni elemento possiamo pensare di applicare una rotazione seguita da una traslazione di metà della dimensione della faccia, ad esempio:

div.square.left {
  background: green;
  transform: rotateY(90deg) translateZ(-400px);
}

allo stesso modo risolviamo la disposizione delle altre facce:

div.square.bottom {
  background: red;
  transform: rotateX(90deg) translateZ(-400px);
}
div.square.right{
  background: blue;
  transform: rotateY(270deg) translateZ(-400px);
}
div.square.front{
  background: yellow;
  transform: rotateY(0deg) translateZ(-400px);
}
div.square.top{
  background: purple;
  transform: rotateX(270deg) translateZ(-400px);
}
div.square.back{
  background: orange;
  transform: rotateY(180deg) translateZ(-400px);
}

Aggiungiamo il comodissimo Prefix Free di Lea Verou per l'aggiunta automatica dei prefissi sperimentali

<!-- scaricalo da http://leaverou.github.com/prefixfree/ -->
<script src="../prefixfree.js"></script>

e testiamo quanto sviluppato finora:

Figura 2. (clic per ingrandire)


Ovviamente la faccia più vicina alla telecamere oscura le altre e non abbiamo ancora sviluppato nessun legame tra l'oggetto che abbiamo costruito e le Device Orientation API. Ma rimediamo subito.

Per complicare la situazione, e rendere più interessante l'esperimento, in questo caso attingiamo dalle informazioni prelevate da tutte e tre le variabili; in questa condizione è importante mantenere l'ordine della trasformazione inverso rispetto a alle variabili ricevute, quindi gamma, beta e alpha:

window.addEventListener ("deviceorientation", traccia, false);
function traccia(evento) {
  document.getElementById('container').style['-webkit-transform'] =
          "translateZ(-800px)" +
          "rotateY(" + ( -evento.gamma ) + "deg) " +
          "rotateX(" + evento.beta + "deg) " +
          "rotateZ(" + ( evento.alpha ) + "deg) ";
  document.getElementById('container').style['transform'] =
  document.getElementById('container').style['-webkit-transform'];
}

Aggiorniamo la pagina sul nostro device per apprezzare il risultato:

Figura 3. (clic per ingrandire)


Dentro al cubo

Ma cosa succede se spostiamo la telecamera all'interno del cubo? Per farlo e sufficiente modificare una sola riga nel progetto precedente, nella fattispecie la proprietà transform del #container traslando il cubo lungo l'asse z fino a portare la telecamera all'interno di esso:

transform: translateZ(800px) rotateY(0deg);

Aggiorniamo la nostra demo e potremo compiacerci di aver creato un semplicissimo sistema per gestire un tour vrtuale a 360° dove l'utente può indirizzare la telecamera orientando il proprio device.

Figura 4. (clic per ingrandire)


Ma non è finita, costruendo una cubemap, o utilizzandone una dalla rete, e associando ad ogni faccia del nostro cubo la giusta porzione della cubemap è possibile ottenere un vero e proprio panorama esplorabile a 360 gradi, ecco uno screenshot dimostrativo:

Figura 5. (clic per ingrandire)


Veicolare le informazioni con i WebSocket

Il funzionamento dei WebSocket è abbastanza semplice, fondamentalmente queste API consentono di stabilire un canale di comunicazione bidirezionale tra un client ed un server, abilitando in questo modo la possibilità che sia il server ad inviare informazioni senza che il client le richieda.

Questo meccanismo è alla base di molte applicazioni asincrone, chat in primis, ma può essere utilizzato
anche in altri ed interessanti modi.

Ad esempio cosa succedesse se decidessimo di trasmettere attraverso WebSocket le informazioni recuperate delle Device Orientation API? Lo scenario potrebbe essere il seguente: un device viene manipolato, le sue informazioni trasmesse ad un server e poi ad un altro client, all'interno del quale vengono utilizzate per agire su quanto visualizzato. Lo stesso comportamento che sperimentiamo ogniqualvolta ci cimentiamo con la console Wii.

Operiamo sull'esempio precedente facendo in modo che la rotazione del cubo possa essere pilotata da un device remoto. In questo caso quindi il cubo verrà proiettato su di uno schermo desktop mentre il device avrà il ruolo di joystick.

La comunicazione attraverso i WebSocket è sufficientemente semplice, almeno nei limiti di quanto serve per questa demo. Ecco come cambia il javascript dell'esempio precedente:

/* ricordarsi di inserire il corretto indirizzo del server */
var socket = new WebSocket("ws://0.0.0.0:8080");
socket.addEventListener("open", registratiMaster, false);
socket.addEventListener("message", stampaMessaggio, false);
function registratiMaster(evento){
  socket.send('register master');
}
function stampaMessaggio(evento){
  var angoli = JSON.parse(evento.data);
  document.getElementById('container').style['-webkit-transform'] =
         "translateZ(-400px) " +
         "rotateZ(" + ( -angoli.gamma ) + "deg) " +
         "rotateX(" + angoli.beta + "deg) " +
         "rotateY(" + angoli.alpha + "deg)";
  document.getElementById('container').style['transform'] =
  document.getElementById('container').style['-webkit-transform'];
}

Come possiamo notare in questo caso le valorizzazioni delle tre rotazioni non provengono più dall'evento deviceorientation ma da message, ovverossia dall'evento che viene generato ogniqualvolta un nuovo messaggio arrivi dal server presso il quale ci siamo registrati istanziando un nuovo oggetto WebSocket.

Ora dobbiamo costruire una pagina HTML progettata per essere eseguita nel device e per inviare le informazioni alpha beta e gamma allo stesso server, che dovrà poi provvedere ad instradarle verso il client contente il cubo che abbiamo visto poche righe fa:

<!doctype html>
<html>
<head>
<meta charset="utf8">
<title>Pilota il cubo</title>
<meta name="viewport"
      content="width=device-width,
	           initial-scale=1.0,
               maximum-scale=1.0,
			   user-scalable=no"/>
<script>
/* ricordarsi di specificare l'ip del server WebSocket */
var socket = new WebSocket("ws://0.0.0.0:8080");
socket.addEventListener("open", prontoPerMuovere, false);
socket.addEventListener("message", nuovoMessaggio, false);
function prontoPerMuovere(){
  window.addEventListener ("deviceorientation", muovi, false);
}
function muovi(evento){
  socket.send(JSON.stringify({
    alpha: evento.alpha,
    beta: evento.beta,
    gamma: evento.gamma
  }));
}
</script>
</head>
<body>
</body>
</html>

Anche in questo caso il codice è molto facile da interpretare, all'apertura del socket (evento open) ci mettiamo in ascolto del solito evento deviceorientation, questa volta però, al posto che utilizzare le informazioni direttamente, le serializziamo in JSON e le inviamo al WebSocket, che dovrà inviarle a sua volta al client desktop che abbiamo sviluppato poco fa.

Non resta che sviluppare il server, il cui unico compito consiste nell'instradare correttamente i messaggi tra i due client. Utilizzeremo Ruby che riesce a mantenere un'alta leggibilità pur garantendo un codice succinto.

Ecco le poche righe che compongono il server (server.rb):

require 'em-websocket'
EventMachine.run do
  @channel = EM::Channel.new
  EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
    ws.onmessage do |msg|
      case msg
        when 'register master'
          puts "the master is ready"
          @channel.subscribe { |m| ws.send m }
        else
          puts "sending #{msg} to master"
          @channel.push msg
      end
    end
  end
end

Come possiamo notare il server identifica il master, ovverosia il client che contiene il cubo, da un messaggio 'register master'; una volta avvenuta questa identificazione ogni successivo messaggio proveniente da qualsiasi altro client verrà semplicemente instradato verso il master.

Ovviamente l'implementazione è molto naif, consente l'esecuzione di un solo master per volta e non tiene d'acconto di nessuna tematica relativa alla sicurezza della comunicazione, però rimane funzionante ed efficiente.

Per provare questa demo è necessario premunirsi di Ruby on Rails e della gemma 'em-websocket' che può essere installata eseguendo da un terminale:

gem install em-websocket

Ora è sufficiente eseguire il server (ruby server.rb) e successivamente accedere alla pagina master da un qualsiasi desktop ed aprire con un device mobile l'altra pagina sviluppata. Se abbiamo impostato correttamente gli ip e le due istanze condividono la medesima rete dovremmo poter pilotare l'orientamento del cubo su desktop semplicemente inclinando il device mobile che teniamo in mano.

Conclusioni

WebSocket e Device Orientation API si prestano a tutta una serie di sviluppi molto interessanti sia per l'ambito ludico che no. Al momento purtroppo il supporto è ancora troppo scarso per poter considerare questa tecnologia production ready ma tenendo conto dell'attuale evoluzione da parte dei browser mobile sono confidente che questo problema possa risolversi nel breve periodo.

Ti consigliamo anche