Introduzione
Aggiornamenti in rotazione, transizioni tra pagine discontinue e ritardi periodici negli eventi di tocco sono solo alcuni dei problemi degli ambienti web mobile di oggi. Gli sviluppatori cercano di avvicinarsi il più possibile alla versione nativa, ma spesso vengono sviati da hack, reset e framework rigidi.
In questo articolo, illustreremo le nozioni di base necessarie per creare un'app web mobile HTML5. L'obiettivo principale è svelare le complessità nascoste che i framework mobile di oggi cercano di nascondere. Vedrai un approccio minimalista (che utilizza le API HTML5 di base) e i fondamenti di base che ti consentiranno di scrivere il tuo framework o di contribuire a quello che utilizzi attualmente.
Accelerazione hardware
In genere, le GPU gestiscono la modellazione 3D dettagliata o i diagrammi CAD, ma in questo caso vogliamo che i nostri disegni primitivi (div, sfondi, testo con ombreggiatura interna, immagini e così via) vengano visualizzati e animati in modo fluido tramite la GPU. La cosa sfortunata è che la maggior parte degli sviluppatori di front-end sta trasferendo questo processo di animazione a un framework di terze parti senza preoccuparsi della semantica, ma queste funzionalità principali di CSS3 dovrebbero essere mascherate? Ecco alcuni motivi per cui è importante prestare attenzione a questi aspetti:
Allocazone della memoria e carico computazionale: se componi ogni elemento del DOM solo per l'accelerazione hardware, la persona che lavorerà al tuo codice potrebbe rintracciarti e picchiarti duramente.
Consumo energetico: ovviamente, quando si attiva l'hardware, si attiva anche la batteria. Quando sviluppano per il mobile, gli sviluppatori sono costretti a prendere in considerazione l'ampia gamma di vincoli dei dispositivi durante la scrittura di app web mobile. Questo fenomeno sarà ancora più diffuso man mano che i produttori di browser inizieranno a consentire l'accesso a sempre più hardware dei dispositivi.
Conflitti: ho riscontrato comportamenti errati durante l'applicazione dell'accelerazione hardware a parti della pagina già accelerate. Pertanto, è molto importante sapere se hai accelerazioni sovrapposte.
Per rendere l'interazione utente fluida e il più simile possibile agli annunci nativi, dobbiamo fare in modo che il browser funzioni al nostro posto. Idealmente, vogliamo che la CPU del dispositivo mobile configuri l'animazione iniziale, quindi che la GPU si occupi solo della composizione di diversi livelli durante il processo di animazione. È ciò che fanno translate3d, scale3d e translateZ: assegnano agli elementi animati un proprio livello, consentendo così al dispositivo di eseguire il rendering di tutto in modo fluido. Per scoprire di più sul compositing accelerato e sul funzionamento di WebKit, Ariya Hidayat ha molte informazioni utili nel suo blog.
Transizioni di pagina
Diamo un'occhiata a tre degli approcci di interazione con l'utente più comuni durante lo sviluppo di un'app web mobile: effetti di scorrimento, rotazione e capovolgimento.
Puoi visualizzare questo codice in azione qui http://slidfast.appspot.com/slide-flip-rotate.html (nota: questa demo è progettata per un dispositivo mobile, quindi avvia un emulatore, usa il tuo smartphone o tablet o riduci le dimensioni della finestra del browser a circa 1024 px o meno).
Per prima cosa, esamineremo le transizioni slide, flip e rotazione e come vengono accelerate. Nota che ogni animazione richiede solo tre o quattro righe di CSS e JavaScript.
A scorrimento
Il più comune dei tre approcci di transizione, le transizioni di pagine scorrevoli simulano l'aspetto nativo delle applicazioni mobile. La transizione delle diapositive viene invocata per inserire una nuova area di contenuti nella visualizzazione.
Per l'effetto slide, per prima cosa dichiariamo 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>
Notate come abbiamo questo concetto di pagine di staging a sinistra o a destra. Potrebbe essere qualsiasi direzione, ma questa è la più comune.
Ora abbiamo l'animazione e l'accelerazione hardware con solo poche righe di CSS. L'animazione effettiva avviene quando scambiamo 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 "magico".
Quando l'utente fa clic su un elemento di navigazione, viene eseguito il seguente codice JavaScript per scambiare le classi. Non vengono utilizzati framework di terze parti, è solo 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. Ci affidiamo completamente a CSS3 per il lavoro più impegnativo.
.stage-left {
left: -480px;
}
.stage-right {
left: 480px;
}
.stage-center {
top: 0;
left: 0;
}
Esaminiamo ora il CSS che gestisce il rilevamento e l'orientamento dei dispositivi mobili. Potremmo applicare ogni dispositivo e ogni risoluzione (vedi Risoluzione delle query multimediali). In questa demo ho utilizzato solo alcuni semplici esempi per coprire la maggior parte delle visualizzazioni in verticale e orizzontale sui dispositivi mobili. Questa opzione è 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 siano 2D o 3D), ha senso creare una query sui media ed escludere l'accelerazione a quel livello. Tieni presente che i trucchi di 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, la rotazione è nota come scorrimento della pagina. Qui utilizziamo un semplice codice JavaScript per gestire questo evento sui dispositivi iOS e Android (basati su WebKit).
Guardala in azione all'indirizzo http://slidfast.appspot.com/slide-flip-rotate.html.
Per quanto riguarda gli eventi di tocco e le transizioni, la prima cosa da fare è capire la posizione corrente dell'elemento. Per ulteriori informazioni su WebKitCSSMatrix, consulta questa documentazione.
function pageMove(event) {
// get position after transform
var curTransform = new WebKitCSSMatrix(window.getComputedStyle(page).webkitTransform);
var pagePosition = curTransform.m41;
}
Poiché utilizziamo una transizione CSS3 con decelerazione per il passaggio di pagina, il solito element.offsetLeft
non funzionerà.
A questo punto dobbiamo capire in quale direzione sta girando l'utente e impostare una soglia per la visualizzazione di un evento (navigazione tra le pagine).
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 anche swipeTime
in millisecondi. Ciò consente di attivare l'evento di navigazione se l'utente fa scorrere rapidamente lo schermo per voltare pagina.
Per posizionare la pagina e rendere le animazioni native 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 giocare con cubic-bezier per dare un'esperienza nativa ottimale alle transizioni, ma ease-out ha fatto il trucco.
Infine, per eseguire la navigazione, dobbiamo chiamare i metodi slideTo()
definiti in precedenza 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
Esaminiamo ora l'animazione di rotazione utilizzata in questa demo. Puoi ruotare in qualsiasi momento la pagina che stai visualizzando di 180 gradi per rivelare il lato opposto toccando l'opzione di menu "Contatto". Anche in questo caso, sono necessarie solo alcune righe di CSS e un po' di JavaScript per assegnare una classe di transizione onclick
.
NOTA: la transizione di rotazione non viene visualizzata correttamente sulla maggior parte delle versioni di Android perché non sono disponibili le funzionalità di trasformazione CSS 3D. Purtroppo, anziché ignorare la rotazione, Android fa ruotare la pagina invece di capovolgerla. Ti consigliamo di utilizzare questa transizione con parsimonia finché il supporto non migliorerà.
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 esaminato le transizioni di base, diamo un'occhiata alla loro meccanica e al loro compositing.
Per realizzare questa magica sessione di debug, apriamo un paio di browser e l'IDE che preferisci. Per prima cosa, 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 il terminale e digita quanto segue:
- $> export CA_COLOR_OPAQUE=1
- $> export CA_LOG_MEMORY_USAGE=1
- $> /Applications/Safari.app/Contents/MacOS/Safari
Viene avviato Safari con un paio di utility di debug. CA_COLOR_OPAQUE ci mostra quali elementi vengono effettivamente compositi o accelerati. CA_LOG_MEMORY_USAGE indica la quantità di memoria utilizzata quando inviamo le operazioni di disegno al backing store. In questo modo puoi capire esattamente quanto stress stai mettendo sul dispositivo mobile e, eventualmente, avere indicazioni su come l'utilizzo della GPU potrebbe scaricare la batteria del dispositivo di destinazione.
Ora avvia Chrome per visualizzare alcune informazioni utili sui frame al secondo (FPS):
- Apri il browser web Google Chrome.
- Nella barra degli URL, digita about:flags.
- Scorri verso il basso alcuni elementi e fai clic su "Attiva" per il contatore FPS.
Se visualizzi questa pagina nella versione potenziata di Chrome, vedrai il contatore FPS rosso nell'angolo in alto a sinistra.
In questo modo sappiamo che l'accelerazione hardware è attivata. Ci dà anche un'idea di come funziona l'animazione e di eventuali perdite (animazioni in esecuzione continua che devono essere interrotte).
Un altro modo per visualizzare l'accelerazione hardware è aprire la stessa pagina in Safari (con le variabili di ambiente menzionate sopra). Ogni elemento DOM accelerato ha una sfumatura rossa. Questo ci mostra esattamente cosa viene composto per livello. Nota che la navigazione bianca non è rossa perché non è accelerata.
Un'impostazione simile per Chrome è disponibile anche in about:flags "Bordi dei livelli di rendering compositi".
Un altro ottimo modo per vedere i livelli compositi è visualizzare la demo delle foglie cadenti di WebKit con questa mod applicata.
Infine, per comprendere appieno le prestazioni dell'hardware grafico della nostra applicazione, diamo un'occhiata al modo in cui viene consumata la memoria. Qui vediamo che stiamo inviando 1,38 MB di istruzioni di disegno ai buffer CoreAnimation su macOS. I buffer della memoria di Core Animation sono condivisi tra OpenGL ES e la GPU per creare i pixel finali che vedi sullo schermo.
Quando semplicemente ridimensioniamo o ingrandiamo la finestra del browser, notiamo che anche la memoria si espande.
Questo ti dà un'idea di come viene utilizzata la memoria sul tuo dispositivo mobile solo se redimensioni il browser alle dimensioni corrette. Se stavi eseguendo il debug o il test per gli ambienti iPhone, ridimensiona l'immagine a 480 x 320 px. Ora sappiamo esattamente come funziona l'accelerazione hardware e cosa serve per eseguire il debug. Una cosa è leggerne, ma vedere effettivamente il funzionamento dei buffer della memoria GPU è un'altra.
Dietro le quinte: recupero e memorizzazione nella cache
È arrivato il momento di portare la memorizzazione nella cache di pagine e risorse a un livello superiore. In modo simile all'approccio utilizzato da JQuery Mobile e da framework simili, preleveremo e memorizzeremo nella cache le nostre pagine con chiamate AJAX concorrenti.
Vediamo alcuni problemi principali del web mobile e i motivi per cui dobbiamo affrontarli:
- Recupero: il precaricamento delle nostre pagine consente agli utenti di mettere l'app offline e di non dover attendere tra un'azione di navigazione e l'altra. Naturalmente, non vogliamo bloccare la larghezza di banda del dispositivo quando si connette a internet, quindi dobbiamo utilizzare questa funzionalità con parsimonia.
- Memorizzazione nella cache: in seguito, vogliamo un approccio concorrente o asincrono per il recupero e la memorizzazione nella cache di queste pagine. Dobbiamo anche usare localStorage (poiché è ben supportato tra i vari 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 di 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 di scorrimento, rotazione e capovolgimento, iniziamo aggiungendo alcune pagine secondarie e creando i link per accedervi. Esaminiamo i link e creiamo le transizioni all'istante.
Visualizza la demo di recupero e memorizzazione nella cache qui.
Come puoi vedere, stiamo sfruttando il markup semantico. Solo un link a un'altra pagina. La pagina secondaria segue la stessa struttura di nodi/classi della pagina principale. Potremmo fare un ulteriore passo avanti e utilizzare l'attributo data-* per i nodi "page" e così via. Ecco la pagina dei dettagli (secondaria) in un file HTML separato (/demo2/home-detail.html) che verrà caricata, memorizzata nella cache e configurata 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 al codice JavaScript. Per semplicità, non ho incluso nel codice eventuali helper o ottimizzazioni. Qui non facciamo altro che eseguire un ciclo in un array specificato di nodi DOM per estrarre i link da recuperare e memorizzare nella cache.
Nota: per questa demo, questo metodo fetchAndCache()
viene chiamato al caricamento della pagina. Lo rielaboriamo nella sezione successiva quando rileviamo la connessione di rete e determiniamo quando deve essere chiamato.
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') &&
//'#' 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) &&
//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 tramite l'utilizzo dell'oggetto "AJAX". Una spiegazione più avanzata sull'utilizzo di localStorage all'interno di una chiamata AJAX è disponibile in Lavorare off-grid con HTML5 offline. In questo esempio viene mostrato l'utilizzo di base della memorizzazione nella cache per ogni richiesta e la fornitura degli oggetti memorizzati nella cache quando il server restituisce qualsiasi cosa diversa da una risposta positiva (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;
}
}
}
}
Purtroppo, poiché localStorage utilizza UTF-16 per la codifica dei caratteri, ogni singolo byte viene memorizzato come 2 byte, portando il limite di spazio di archiviazione da 5 MB a 2,6 MB in totale. Il motivo per cui recuperare e memorizzare nella cache queste pagine/questo markup al di fuori dell'ambito della cache dell'applicazione viene spiegato nella sezione successiva.
Grazie ai recenti progressi dell'elemento iframe con HTML5, ora abbiamo un modo semplice ed efficace per analizzare il responseText
che riceviamo dalla chiamata AJAX. Esistono molti analizzatori JavaScript di 3000 righe ed espressioni regolari che rimuovono i tag script e così via. Ma perché non lasciare che sia il browser a fare ciò che sa fare meglio? In questo esempio, scriveremo il responseText
in un iframe temporaneo nascosto. Utilizziamo l'attributo "sandbox" HTML5 che disattiva gli script e offre molte funzionalità di sicurezza…
Dalla specifica: L'attributo sandbox, se specificato, attiva un insieme di limitazioni aggiuntive su qualsiasi contenuto ospitato dall'iframe. Il suo valore deve essere un insieme non ordinato di token univoci separati da spazi, senza distinzione tra maiuscole e minuscole in ASCII. I valori consentiti sono allow-forms, allow-same-origin, allow-scripts e allow-top-navigation. Quando l'attributo è impostato, i contenuti vengono considerati 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 si rifiuta di spostare implicitamente un nodo da un documento all'altro. Viene generato un errore se il nuovo nodo figlio è stato creato in un altro documento. Qui utilizziamo adoptNode
e non ci sono problemi.
Perché scegliere l'iframe? Perché non utilizzare semplicemente innerHTML? Anche se innerHTML ora fa parte della specifica HTML5, è una pratica pericolosa inserire la risposta di un server (buono o cattivo) in un'area non controllata. Durante la stesura di questo articolo, non ho trovato nessuno che utilizzasse altro che innerHTML. So che JQuery lo utilizza come base con un fallback di accodamento solo in caso di eccezione. E anche JQuery Mobile lo utilizza. Tuttavia non ho eseguito test approfonditi per quanto riguarda il "funzionamento casuale" di innerHTML, ma sarebbe molto interessante vedere tutte le piattaforme interessate. Sarebbe anche interessante capire quale approccio ha un rendimento migliore… ho sentito affermazioni in tal senso da entrambe le parti.
Rilevamento, gestione e profilazione del tipo di rete
Ora che abbiamo la possibilità di mettere in buffer (o in cache predittiva) la nostra app web, dobbiamo fornire le funzionalità di rilevamento della connessione appropriate che rendono la nostra app 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 del pubblico alza la mano e chiede: "A cosa serve?". Ecco un possibile modo per configurare un'app web mobile estremamente intelligente.
Primo scenario di buon senso... Durante l'interazione con il Web da un dispositivo mobile su un treno ad alta velocità, la rete potrebbe benissimo scomparire in vari momenti e aree geografiche diverse 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 seguente codice fornisce:
- Accesso offline tramite
applicationCache
. - Rileva se aggiunto ai preferiti e offline.
- Rileva il passaggio da offline a online e viceversa.
- Rileva le connessioni lente e recupera i contenuti in base al tipo di rete.
Ancora una volta, 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);
Negli EventListener sopra, dobbiamo dire al nostro codice se viene chiamato da un evento o da una richiesta o un aggiornamento della pagina effettivi. Il motivo principale è che l'evento onload
corpo non verrà attivato passando dalla modalità online a quella offline e viceversa.
Poi abbiamo un semplice controllo per un evento ononline
o onload
. Questo codice reimposta i link disattivati quando passi da offline a online, ma se questa app fosse più sofisticata, potresti inserire una logica che riprenda il recupero dei contenuti o gestisca l'esperienza utente per le 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()
. Qui puoi modificare l'app per la modalità offline e provare a recuperare in background le transazioni in corso. Il codice riportato di seguito estrae tutti i nostri link esterni e li disattiva, intrappolando gli utenti nella nostra app offline PER SEMPRE, muhahaha!
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 alla parte interessante. Ora che la nostra app conosce lo stato di connessione, possiamo anche controllare il tipo di connessione quando è online e apportare le modifiche necessarie. Ho elencato le latenze e le velocità in download dei fornitori nordamericani tipici nei commenti per ogni 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);
}
}
Potremmo apportare numerosi aggiustamenti al nostro processo fetchAndCache, ma qui ho semplicemente indicato di recuperare le risorse in modo asincrono (true) o sincrono (false) per una determinata connessione.
Cronologia delle richieste Edge (sincrone)
Cronologia delle richieste Wi-Fi (asincrona)
Ciò consente almeno un metodo di regolazione dell'esperienza utente in base a connessioni lente o veloci. Non si tratta in alcun modo di una soluzione definitiva. Un'altra cosa da fare è mostrare una finestra modale di caricamento quando si fa clic su un link (su 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 ultime novità di HTML5. Guarda la demo sul rilevamento della rete qui.
Conclusione
Il percorso delle app mobile HTML5 è solo all'inizio. Ora hai visto le basi molto semplici e di base di un "framework" mobile costruito esclusivamente su HTML5 e sulle relative tecnologie di supporto. Penso che sia importante che gli sviluppatori lavorino con queste funzionalità e li approfondiscano, in modo che non vengano mascherati da un wrapper.