Tecniche HTML5 per ottimizzare il rendimento sui dispositivi mobili

Wesley Hales
Wesley Hales

Introduzione

Aggiornamenti continui, transizioni di pagina discontinua e ritardi periodici negli eventi di tocco sono solo alcuni dei problemi negli ambienti web mobile di oggi. Gli sviluppatori cercano di avvicinarsi il più possibile agli stili nativi, ma spesso vengono deragliati da hack, reset e framework rigidi.

In questo articolo, illustreremo il minimo indispensabile per creare un'app web HTML5 per dispositivi mobili. L'aspetto principale è quello di mettere in luce le complessità nascoste che i framework per dispositivi mobili odierni cercano di nascondere. Vedrai un approccio minimalista (utilizzando le API HTML5 di base) e concetti fondamentali di base che ti consentiranno di scrivere il tuo framework o di contribuire a quello che utilizzi attualmente.

Accelerazione hardware

Normalmente le GPU gestiscono modelli 3D o diagrammi CAD dettagliati, ma in questo caso vogliamo che i nostri disegni primitivi (div, sfondi, testo con ombreggiature, immagini e così via) siano uniformi e animati in modo uniforme tramite la GPU. La cosa sfortunata è che la maggior parte degli sviluppatori front-end sta trasferendo questo processo di animazione a un framework di terze parti senza preoccuparsi della semantica, ma queste funzionalità CSS3 principali dovrebbero essere mascherate? Vediamo alcuni motivi per cui la cura di queste cose è importante:

  1. Allocazione della memoria e carico di calcolo: se ti capita di comporre ogni elemento nel DOM solo per il bene dell'accelerazione hardware, la persona successiva che lavora sul tuo codice potrebbe inseguirti e picchiarti duramente.

  2. Consumo energetico: ovviamente, quando si inserisce l'hardware, viene attivata anche la batteria. Durante lo sviluppo per i dispositivi mobili, gli sviluppatori sono costretti a prendere in considerazione l'ampia gamma di vincoli relativi ai dispositivi durante la scrittura di app web per dispositivi mobili. Ciò sarà ancora più diffuso poiché i produttori di browser inizieranno ad attivare l'accesso a una quantità sempre maggiore di hardware dei dispositivi.

  3. Conflitti: ho riscontrato un comportamento glitch durante l'applicazione dell'accelerazione hardware a parti della pagina che erano già accelerate. Sapere se esistono accelerazioni sovrapposte è molto importante.

Per rendere l'interazione dell'utente il più fluida possibile e simile a quella nativa, dobbiamo far funzionare il browser per noi. Idealmente, la CPU del dispositivo mobile deve impostare l'animazione iniziale e poi è la GPU responsabile della composizione dei vari livelli durante il processo di animazione. Questo è ciò che permettono di tradurre3d, scale3d e TranslateZ: dotano gli elementi animati al proprio livello, consentendo così al dispositivo di visualizzare tutto insieme in modo uniforme. Per saperne di più sulla compositing accelerata e sul funzionamento di WebKit, Ariya Hidayat ha molte buone informazioni sul suo blog.

Transizioni di pagina

Diamo un'occhiata a tre degli approcci più comuni all'interazione degli utenti durante lo sviluppo di un'app web mobile: effetti di scorrimento, capovolgimento e rotazione.

Puoi visualizzare questo codice in azione qui: http://slidfast.appspot.com/slide-flip-rotate.html. Nota: questa demo è stata sviluppata per i dispositivi mobili, quindi avvia un emulatore, utilizza il tuo telefono o tablet oppure riduci le dimensioni della finestra del browser a circa 1024 px o meno.

Per prima cosa, esamineremo le transizioni di tipo slide, flip e rotazione e come vengono accelerate. Nota che ogni animazione richiede solo tre o quattro righe di codice CSS e JavaScript.

A scorrimento

Il più comune dei tre approcci di transizione, le transizioni di pagina scorrevoli imitano l'aspetto nativo delle applicazioni mobile. La transizione delle slide viene richiamata per portare una nuova area di contenuti nella visualizzazione.

Per l'effetto slide, dichiariamo innanzitutto il nostro markup:

<div id="home-page" class="page">
  <h1>Home Page</h1>
</div>

<div id="products-page" class="page stage-right">
  <h1>Products Page</h1>
</div>

<div id="about-page" class="page stage-left">
  <h1>About Page</h1>
</div>

Nota che questo concetto di pagine di gestione temporanea è a destra o a sinistra. Potrebbe essere qualsiasi direzione, ma questa è la più comune.

Ora abbiamo l'animazione e l'accelerazione hardware con poche righe di CSS. L'animazione effettiva si verifica quando invertiamo le classi negli elementi div della pagina.

.page {
  position: absolute;
  width: 100%;
  height: 100%;
  /*activate the GPU for compositing each page */
  -webkit-transform: translate3d(0, 0, 0);
}

translate3d(0,0,0) è noto come approccio "silver bullet".

Quando l'utente fa clic su un elemento di navigazione, viene eseguito il seguente codice JavaScript per scambiare le classi. Non viene utilizzato alcun framework di terze parti. Si tratta di un semplice codice JavaScript. ;)

function getElement(id) {
  return document.getElementById(id);
}

function slideTo(id) {
  //1.) the page we are bringing into focus dictates how
  // the current page will exit. So let's see what classes
  // our incoming page is using. We know it will have stage[right|left|etc...]
  var classes = getElement(id).className.split(' ');

  //2.) decide if the incoming page is assigned to right or left
  // (-1 if no match)
  var stageType = classes.indexOf('stage-left');

  //3.) on initial page load focusPage is null, so we need
  // to set the default page which we're currently seeing.
  if (FOCUS_PAGE == null) {
    // use home page
    FOCUS_PAGE = getElement('home-page');
  }

  //4.) decide how this focused page should exit.
  if (stageType > 0) {
    FOCUS_PAGE.className = 'page transition stage-right';
  } else {
    FOCUS_PAGE.className = 'page transition stage-left';
  }

  //5. refresh/set the global variable
  FOCUS_PAGE = getElement(id);

  //6. Bring in the new page.
  FOCUS_PAGE.className = 'page transition stage-center';
}

stage-left o stage-right diventa stage-center e forza lo scorrimento della pagina nella visualizzazione centrale. Il lavoro più impegnativo dipende completamente da CSS3.

.stage-left {
  left: -480px;
}

.stage-right {
  left: 480px;
}

.stage-center {
  top: 0;
  left: 0;
}

Ora diamo un'occhiata al CSS che gestisce il rilevamento e l'orientamento dei dispositivi mobili. Potremmo intervenire su ogni dispositivo e ogni risoluzione (vedi la risoluzione delle query multimediali). In questa demo ho utilizzato solo alcuni semplici esempi per trattare la maggior parte delle visualizzazioni in verticale e orizzontale sui dispositivi mobili. È utile anche per applicare l'accelerazione hardware per dispositivo. Ad esempio, poiché la versione desktop di WebKit accelera tutti gli elementi trasformati (indipendentemente dal fatto che sia 2D o 3D), ha senso creare una query supporti ed escludere l'accelerazione a quel livello. Tieni presente che i trucchi per l'accelerazione hardware non offrono alcun miglioramento della velocità in Android Froyo 2.2 e versioni successive. Tutta la composizione viene eseguita all'interno del software.

/* iOS/android phone landscape screen width*/
@media screen and (max-device-width: 480px) and (orientation:landscape) {
  .stage-left {
    left: -480px;
  }

  .stage-right {
    left: 480px;
  }

  .page {
    width: 480px;
  }
}

Inversione

Sui dispositivi mobili, girare è nota come far scorrere la pagina via. Qui utilizziamo alcuni semplici comandi JavaScript per gestire questo evento sui dispositivi iOS e Android (basati su WebKit).

Guarda come funziona http://slidfast.appspot.com/slide-flip-rotate.html.

Per quanto riguarda gli eventi di tocco e le transizioni, la prima cosa da fare è ottenere un handle sulla posizione corrente dell'elemento. Consulta questo documento per ulteriori informazioni su WebKitCSSMatrix.

function pageMove(event) {
  // get position after transform
  var curTransform = new WebKitCSSMatrix(window.getComputedStyle(page).webkitTransform);
  var pagePosition = curTransform.m41;
}

Poiché stiamo utilizzando una transizione di facilitazione CSS3 per il cambio di pagina, il valore element.offsetLeft standard non funzionerà.

Successivamente, devi capire in quale direzione l'utente sta capovolgendo e impostare una soglia per la realizzazione di un evento (navigazione nella pagina).

if (pagePosition >= 0) {
 //moving current page to the right
 //so means we're flipping backwards
   if ((pagePosition > pageFlipThreshold) || (swipeTime < swipeThreshold)) {
     //user wants to go backward
     slideDirection = 'right';
   } else {
     slideDirection = null;
   }
} else {
  //current page is sliding to the left
  if ((swipeTime < swipeThreshold) || (pagePosition < pageFlipThreshold)) {
    //user wants to go forward
    slideDirection = 'left';
  } else {
    slideDirection = null;
  }
}

Noterai inoltre che stiamo misurando swipeTime in millisecondi. In questo modo l'evento di navigazione viene attivato se l'utente fa scorrere rapidamente lo schermo per voltare pagina.

Per posizionare la pagina e far sembrare native le animazioni mentre un dito tocca lo schermo, utilizziamo le transizioni CSS3 dopo l'attivazione di ogni evento.

function positionPage(end) {
  page.style.webkitTransform = 'translate3d('+ currentPos + 'px, 0, 0)';
  if (end) {
    page.style.WebkitTransition = 'all .4s ease-out';
    //page.style.WebkitTransition = 'all .4s cubic-bezier(0,.58,.58,1)'
  } else {
    page.style.WebkitTransition = 'all .2s ease-out';
  }
  page.style.WebkitUserSelect = 'none';
}

Ho provato a sperimentare con la curva di Bézier cubica per dare la migliore sensazione nativa per le transizioni, ma la facilità di gioco ha fatto la cosa giusta.

Infine, per rendere possibile la navigazione, dobbiamo chiamare i metodi slideTo() precedentemente definiti che abbiamo utilizzato nell'ultima demo.

track.ontouchend = function(event) {
  pageMove(event);
  if (slideDirection == 'left') {
    slideTo('products-page');
  } else if (slideDirection == 'right') {
    slideTo('home-page');
  }
}

Rotazione

Ora diamo un'occhiata all'animazione di rotazione utilizzata in questa demo. Puoi ruotare la pagina che stai visualizzando in qualsiasi momento di 180 gradi per mostrare il lato opposto toccando l'opzione di menu "Contatto". Anche in questo caso, sono necessarie solo poche righe di codice CSS e codice JavaScript per assegnare una classe di transizione onclick. NOTA: la transizione di rotazione non viene visualizzata correttamente nella maggior parte delle versioni di Android perché non dispone delle funzionalità di trasformazione CSS 3D. Sfortunatamente, invece di ignorare il capovolgimento, Android fa sparire la pagina ruotando la pagina anziché girandola. Ti consigliamo di utilizzare con parsimonia questa transizione fino a quando il supporto non migliora.

Il markup (concetto di base di fronte e retro):

<div id="front" class="normal">
...
</div>
<div id="back" class="flipped">
    <div id="contact-page" class="page">
        <h1>Contact Page</h1>
    </div>
</div>

Il codice JavaScript:

function flip(id) {
  // get a handle on the flippable region
  var front = getElement('front');
  var back = getElement('back');

  // again, just a simple way to see what the state is
  var classes = front.className.split(' ');
  var flipped = classes.indexOf('flipped');

  if (flipped >= 0) {
    // already flipped, so return to original
    front.className = 'normal';
    back.className = 'flipped';
    FLIPPED = false;
  } else {
    // do the flip
    front.className = 'flipped';
    back.className = 'normal';
    FLIPPED = true;
  }
}

Il CSS:

/*----------------------------flip transition */
#back,
#front {
  position: absolute;
  width: 100%;
  height: 100%;
  -webkit-backface-visibility: hidden;
  -webkit-transition-duration: .5s;
  -webkit-transform-style: preserve-3d;
}

.normal {
  -webkit-transform: rotateY(0deg);
}

.flipped {
  -webkit-user-select: element;
  -webkit-transform: rotateY(180deg);
}

Debug dell'accelerazione hardware

Ora che abbiamo visto le transizioni di base, diamo un'occhiata ai meccanismi su cui funzionano e come sono composte.

Per avviare questa magica sessione di debug, attiva un paio di browser e il tuo IDE preferito. Innanzitutto avvia Safari dalla riga di comando per utilizzare alcune variabili di ambiente di debug. Sono su Mac, quindi i comandi potrebbero variare in base al sistema operativo. Apri Terminale e digita quanto segue:

  • $> esporta CA_COLOR_OPAQUE=1
  • $> esporta CA_LOG_MEMORY_USAGE=1
  • $> /Applicazioni/Safari.app/Contents/MacOS/Safari

Safari viene avviato con un paio di helper per il debug. CA_COLOR_OPAQUE mostra quali elementi sono in realtà composti o accelerati. CA_LOG_MEMORY_USAGE mostra quanta memoria stiamo utilizzando per inviare le nostre operazioni di disegno all'archivio di supporto. Questo ti indica esattamente quanta tensione stai mettendo sul dispositivo mobile ed è possibile che tu sappia come l'utilizzo della GPU potrebbe consumare la batteria del dispositivo di destinazione.

Ora attiviamo Chrome in modo da poter vedere alcune informazioni sui frame al secondo (f/s):

  1. Apri il browser web Google Chrome.
  2. Nella barra dell'URL, digita about:flags.
  3. Scorri verso il basso alcuni elementi e fai clic su "Abilita" per Contatore FPS.

Se visualizzi questa pagina nella tua versione potenziata di Chrome, vedrai il contatore FPS rosso nell'angolo in alto a sinistra.

FPS di Chrome

In questo modo sappiamo che l'accelerazione hardware è attivata. Ci dà anche un'idea di come viene eseguita l'animazione e di eventuali perdite (animazioni in esecuzione continua che dovrebbero essere interrotte).

Un altro modo per visualizzare effettivamente l'accelerazione hardware è aprire la stessa pagina in Safari (con le variabili di ambiente di cui sopra). Ogni elemento DOM accelerato ha una tinta rossa. Questo ci mostra esattamente cosa viene composto per strato. Nota che la barra di navigazione bianca non è rossa perché non è accelerata.

Contatto composito

Un'impostazione simile per Chrome è disponibile anche in about:flags "Bordi livello di rendering composto".

Un altro ottimo modo per vedere i livelli compositi è visualizzare la demo per le foglie che cadono WebKit mentre viene applicata questa mod.

Foglie copiate

Infine, per comprendere veramente le prestazioni dell'hardware grafico della nostra applicazione, diamo un'occhiata a come viene consumata la memoria. Qui vediamo che stiamo trasferendo 1,38 MB di istruzioni di disegno ai buffer CoreAnimation su Mac OS. I buffer di memoria Core Animation vengono condivisi tra OpenGL ES e la GPU per creare i pixel finali che vedi sullo schermo.

Coreanimation 1

Quando ridimensioniamo o ingrandiamo semplicemente la finestra del browser, vediamo che si espande anche la memoria.

Coreanimation 2

Questo ti dà un'idea di come viene consumata la memoria sul tuo dispositivo mobile solo se ridimensioni il browser alle dimensioni corrette. Se esegui il debug o il test per gli ambienti iPhone, ridimensionalo a 480 x 320 px. Ora sappiamo esattamente come funziona l'accelerazione hardware e cosa serve per il debug. È una cosa da leggere a riguardo, ma vedere effettivamente i buffer di memoria della GPU funzionano visivamente mettendo le cose in prospettiva.

Dietro le quinte: recupero e memorizzazione nella cache

È arrivato il momento di migliorare la memorizzazione nella cache delle nostre pagine e risorse. Proprio come con l'approccio utilizzato da JQuery Mobile e da framework simili, le pagine verranno precaricate e memorizzate nella cache con chiamate AJAX simultanee.

Esaminiamo alcuni problemi principali del web mobile e le ragioni per cui è necessario farlo:

  • Recupero: il precaricamento delle nostre pagine consente agli utenti di mettere offline l'app e non evita attese tra le azioni di navigazione. Naturalmente non vogliamo soffocare la larghezza di banda del dispositivo quando quest'ultimo è online, quindi dobbiamo usare questa funzione con parsimonia.
  • Memorizzazione nella cache: il secondo è utilizzare un approccio simultaneo o asincrono per il recupero e la memorizzazione nella cache di queste pagine. Dobbiamo anche utilizzare localStorage (poiché è ben supportato dai dispositivi), che purtroppo non è asincrono.
  • AJAX e analisi della risposta: l'utilizzo di innerHTML() per inserire la risposta AJAX nel DOM è pericoloso (e inaffidabile?). Utilizziamo invece un meccanismo affidabile per l'inserimento delle risposte AJAX e la gestione delle chiamate simultanee. Sfruttiamo anche alcune nuove funzionalità di HTML5 per l'analisi del xhr.responseText.

Basandoci sul codice della demo per le slide, il capovolgimento e la rotazione, iniziamo aggiungendo alcune pagine secondarie e aggiungendo dei link. Analizzeremo i link e creeremo le transizioni in tempo reale.

Home page iPhone

Visualizza qui la demo di recupero e cache.

Come puoi vedere, qui stiamo usando il markup semantico. Solo un link a un'altra pagina. La pagina secondaria segue la stessa struttura di nodo/classe della pagina padre. Potremmo andare oltre e utilizzare l'attributo data-* per i nodi "pagina" e così via. Ed ecco la pagina dei dettagli (secondaria) che si trova in un file html separato (/demo2/home-detail.html) che verrà caricato, memorizzato nella cache e configurato per la transizione al caricamento dell'app.

<div id="home-page" class="page">
  <h1>Home Page</h1>
  <a href="demo2/home-detail.html" class="fetch">Find out more about the home page!</a>
</div>

Ora diamo un'occhiata a JavaScript. Per semplicità, non c'è alcun aiuto o ottimizzazione dal codice. Non facciamo altro che eseguire il looping di un array specifico di nodi DOM per estrarre i link da recuperare e memorizzare nella cache. Nota: per questa demo, il metodo fetchAndCache() viene richiamato al caricamento pagina. Viene rielaborata nella sezione successiva, quando rileviamo la connessione di rete e determiniamo quando deve essere chiamata.

var fetchAndCache = function() {
  // iterate through all nodes in this DOM to find all mobile pages we care about
  var pages = document.getElementsByClassName('page');

  for (var i = 0; i < pages.length; i++) {
    // find all links
    var pageLinks = pages[i].getElementsByTagName('a');

    for (var j = 0; j < pageLinks.length; j++) {
      var link = pageLinks[j];

      if (link.hasAttribute('href') &amp;&amp;
      //'#' in the href tells us that this page is already loaded in the DOM - and
      // that it links to a mobile transition/page
         !(/[\#]/g).test(link.href) &amp;&amp;
        //check for an explicit class name setting to fetch this link
        (link.className.indexOf('fetch') >= 0))  {
         //fetch each url concurrently
         var ai = new ajax(link,function(text,url){
              //insert the new mobile page into the DOM
             insertPages(text,url);
         });
         ai.doGet();
      }
    }
  }
};

Garantiamo una corretta post-elaborazione asincrona attraverso l'uso dell'oggetto "AJAX". Una spiegazione più avanzata dell'utilizzo di localStorage all'interno di una chiamata AJAX è disponibile in Lavorare fuori dalla griglia con HTML5 offline. In questo esempio, vediamo l'utilizzo di base della memorizzazione nella cache per ogni richiesta e la fornitura di oggetti memorizzati nella cache quando il server restituisce qualsiasi cosa tranne una risposta riuscita (200).

function processRequest () {
  if (req.readyState == 4) {
    if (req.status == 200) {
      if (supports_local_storage()) {
        localStorage[url] = req.responseText;
      }
      if (callback) callback(req.responseText,url);
    } else {
      // There is an error of some kind, use our cached copy (if available).
      if (!!localStorage[url]) {
        // We have some data cached, return that to the callback.
        callback(localStorage[url],url);
        return;
      }
    }
  }
}

Sfortunatamente, poiché localStorage utilizza la codifica UTF-16 per la codifica dei caratteri, ogni singolo byte viene archiviato come 2 byte, portando il nostro limite di archiviazione da 5 MB a 2,6 MB totali. Nella sezione successiva illustreremo il motivo completo per il recupero e la memorizzazione nella cache di queste pagine/markup al di fuori dell'ambito della cache dell'applicazione.

Con i recenti progressi compiuti nell'elemento iframe con HTML5, ora disponiamo di un modo semplice ed efficace per analizzare i responseText che riceviamo dalla nostra chiamata AJAX. Esistono molti parser JavaScript ed espressioni regolari da 3000 righe che rimuovono i tag di script e così via. Ma perché non lasciare che il browser faccia ciò che sa fare meglio? In questo esempio, scriveremo responseText in un iframe nascosto temporaneo. Stiamo utilizzando l'attributo "sandbox" HTML5 che disattiva gli script e offre molte funzioni di sicurezza...

Dalla specifica: l'attributo sandbox, se specificato, attiva una serie di limitazioni aggiuntive sui contenuti ospitati dall'iframe. Il valore deve essere un insieme non ordinato di token univoci separati da spazi e non sensibili alle maiuscole ASCII. I valori consentiti sono allow-forms, allow-same-origin, allow-scripts e allow-top-navigation. Quando l'attributo è impostato, i contenuti vengono trattati come provenienti da un'origine univoca, i moduli e gli script vengono disattivati, i link non possono avere come target altri contesti di navigazione e i plug-in vengono disattivati.

var insertPages = function(text, originalLink) {
  var frame = getFrame();
  //write the ajax response text to the frame and let
  //the browser do the work
  frame.write(text);

  //now we have a DOM to work with
  var incomingPages = frame.getElementsByClassName('page');

  var pageCount = incomingPages.length;
  for (var i = 0; i < pageCount; i++) {
    //the new page will always be at index 0 because
    //the last one just got popped off the stack with appendChild (below)
    var newPage = incomingPages[0];

    //stage the new pages to the left by default
    newPage.className = 'page stage-left';

    //find out where to insert
    var location = newPage.parentNode.id == 'back' ? 'back' : 'front';

    try {
      // mobile safari will not allow nodes to be transferred from one DOM to another so
      // we must use adoptNode()
      document.getElementById(location).appendChild(document.adoptNode(newPage));
    } catch(e) {
      // todo graceful degradation?
    }
  }
};

Safari rifiuta correttamente di spostare implicitamente un nodo da un documento all'altro. Se il nuovo nodo secondario è stato creato in un altro documento, viene generato un errore. Qui utilizziamo adoptNode e non c'è problema.

Perché utilizzare gli iframe? Perché non utilizzare solo innerHTML? Anche se innerHTML ora fa parte delle specifiche HTML5, è una pratica pericolosa inserire la risposta di un server (cattiva o buona) in un'area non selezionata. Durante la stesura di questo articolo, non ho trovato chiunque utilizzi nient'altro che l'HTML interno. So che JQuery lo usa principalmente con un'aggiunta di riserva solo sull'eccezione. Anche JQuery Mobile la utilizza. Tuttavia, non ho eseguito test rigorosi in merito a "smette di lavorare in modo casuale" innerHTML, ma sarebbe molto interessante vedere tutte le piattaforme interessate. Sarebbe anche interessante vedere quale approccio è più efficace... Ho anche ricevuto affermazioni da entrambe le parti su questo argomento.

Rilevamento, gestione e profilazione del tipo di rete

Ora che abbiamo la possibilità di eseguire il buffer (o la cache predittiva) della nostra app web, dobbiamo fornire le funzionalità di rilevamento della connessione appropriate che la rendono più intelligente. È qui che lo sviluppo di app mobile diventa estremamente sensibile alle modalità online/offline e alla velocità di connessione. Inserisci l'API Network Information. Ogni volta che mostro questa funzionalità in una presentazione, qualcuno tra il pubblico alza la mano e chiede "Per cosa la userei?". Ecco un modo possibile per configurare un'app web mobile estremamente intelligente.

Prima il noioso scenario di buon senso... Quando si interagisce con il Web da un dispositivo mobile su un treno ad alta velocità, la rete potrebbe scomparire molto in vari momenti e diverse aree geografiche potrebbero supportare velocità di trasmissione diverse (ad es. HSPA o 3G potrebbero essere disponibili in alcune aree urbane, ma le aree remote potrebbero supportare tecnologie 2G molto più lente). Il codice seguente risolve la maggior parte degli scenari di connessione.

Il codice seguente fornisce:

  • Accesso offline tramite applicationCache.
  • Rileva se aggiunto ai preferiti e offline.
  • Rileva quando si passa da offline a online e viceversa.
  • Rileva le connessioni lente e recupera i contenuti in base al tipo di rete.

Anche in questo caso, tutte queste funzionalità richiedono pochissimo codice. Innanzitutto, rileviamo i nostri eventi e scenari di caricamento:

window.addEventListener('load', function(e) {
 if (navigator.onLine) {
  // new page load
  processOnline();
 } else {
   // the app is probably already cached and (maybe) bookmarked...
   processOffline();
 }
}, false);

window.addEventListener("offline", function(e) {
  // we just lost our connection and entered offline mode, disable eternal link
  processOffline(e.type);
}, false);

window.addEventListener("online", function(e) {
  // just came back online, enable links
  processOnline(e.type);
}, false);

Nel parametro EventListener riportato sopra, dobbiamo indicare il nostro codice se viene chiamato da un evento o da un'effettiva richiesta o aggiornamento di pagina. Il motivo principale è che l'evento body onload non viene attivato quando si passa dalla modalità online a quella offline e viceversa.

Ora passiamo a un semplice controllo di un evento ononline o onload. Questo codice reimposta i link disattivati quando si passa da offline a online, ma se questa app fosse più sofisticata, potresti inserire una logica che riprenderebbe il recupero dei contenuti o gestisse l'UX per connessioni intermittenti.

function processOnline(eventType) {

  setupApp();
  checkAppCache();

  // reset our once disabled offline links
  if (eventType) {
    for (var i = 0; i < disabledLinks.length; i++) {
      disabledLinks[i].onclick = null;
    }
  }
}

Lo stesso vale per processOffline(). In questo caso puoi manipolare l'app per la modalità offline e provare a recuperare le transazioni avvenute dietro le quinte. Il codice riportato di seguito consente di eliminare tutti i nostri link esterni e li disattiva, bloccando SEMPRE gli utenti nella nostra app offline.

function processOffline() {
  setupApp();

  // disable external links until we come back - setting the bounds of app
  disabledLinks = getUnconvertedLinks(document);

  // helper for onlcick below
  var onclickHelper = function(e) {
    return function(f) {
      alert('This app is currently offline and cannot access the hotness');return false;
    }
  };

  for (var i = 0; i < disabledLinks.length; i++) {
    if (disabledLinks[i].onclick == null) {
      //alert user we're not online
      disabledLinks[i].onclick = onclickHelper(disabledLinks[i].href);

    }
  }
}

Ok, passiamo alle cose belle. Ora che la nostra app conosce lo stato di connessione in cui si trova, possiamo anche controllare il tipo di connessione quando è online e apportare le modifiche necessarie. Ho elencato i download e le latenze tipici dei provider nordamericani nei commenti di ciascuna connessione.

function setupApp(){
  // create a custom object if navigator.connection isn't available
  var connection = navigator.connection || {'type':'0'};
  if (connection.type == 2 || connection.type == 1) {
      //wifi/ethernet
      //Coffee Wifi latency: ~75ms-200ms
      //Home Wifi latency: ~25-35ms
      //Coffee Wifi DL speed: ~550kbps-650kbps
      //Home Wifi DL speed: ~1000kbps-2000kbps
      fetchAndCache(true);
  } else if (connection.type == 3) {
  //edge
      //ATT Edge latency: ~400-600ms
      //ATT Edge DL speed: ~2-10kbps
      fetchAndCache(false);
  } else if (connection.type == 2) {
      //3g
      //ATT 3G latency: ~400ms
      //Verizon 3G latency: ~150-250ms
      //ATT 3G DL speed: ~60-100kbps
      //Verizon 3G DL speed: ~20-70kbps
      fetchAndCache(false);
  } else {
  //unknown
      fetchAndCache(true);
  }
}

Abbiamo potuto apportare numerosi aggiustamenti al nostro processo fetchAndCache, ma ho semplicemente chiesto di recuperare le risorse asincrone (true) o sincrone (false) per una determinata connessione.

Cronologia delle richieste Edge (sincrone)

Sincronizzazione perimetrale

Cronologia delle richieste Wi-Fi (asincrona)

Asinc Wi-Fi

Ciò consente almeno un metodo di modifica dell'esperienza utente in base a connessioni lente o veloci. Questa non è una soluzione definitiva. Un altro consiglio è attivare una finestra modale di caricamento quando si fa clic su un link (in caso di connessioni lente) mentre l'app potrebbe ancora recuperare la pagina del link in background. L'aspetto più importante è ridurre le latenze sfruttando al contempo tutte le funzionalità della connessione dell'utente con le più recenti e avanzate versioni HTML5. Visualizza qui la demo sul rilevamento di rete.

Conclusione

Il viaggio verso le app HTML5 per dispositivi mobili è appena iniziato. Ora puoi vedere i concetti molto semplici e basilari di un "framework" mobile realizzato esclusivamente attorno all'HTML5 e che supporta le sue tecnologie. Penso che sia importante per gli sviluppatori lavorare con queste funzionalità e affrontarle al centro, senza mascherarle da un wrapper.