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
  • beta
  • gamma
    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

    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 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

    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

    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

    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:

    Ora è sufficiente eseguire il server (ruby server.rb

    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