Introduzione
Aggiornamenti rotanti, transizioni di pagina instabili e ritardi periodici negli eventi di tocco sono solo alcuni dei problemi negli ambienti web mobile odierni. Gli sviluppatori cercano di avvicinarsi il più possibile al nativo, ma spesso vengono ostacolati da compromissioni, ripristini e framework rigidi.
In questo articolo, parleremo dei requisiti minimi 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
Normalmente, 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 ombreggiature, immagini e così via) appaiano fluidi e si animino senza problemi tramite la GPU. Il problema è che la maggior parte degli sviluppatori front-end delega questo processo di animazione a un framework di terze parti senza preoccuparsi della semantica, ma queste funzionalità CSS3 di base devono essere mascherate? Ti darò alcuni motivi per cui è importante occuparsi di queste cose:
Allocazione della memoria e carico computazionale: se componi ogni elemento nel DOM solo per l'accelerazione hardware, la persona che lavorerà sul tuo codice potrebbe inseguirti e picchiarti.
Consumo energetico: ovviamente, quando l'hardware si attiva, 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 diventerà ancora più diffuso man mano che i produttori di browser inizieranno ad abilitare l'accesso a un numero sempre maggiore di hardware del dispositivo.
Conflitti: ho riscontrato un comportamento anomalo quando ho applicato l'accelerazione hardware a parti della pagina già accelerate. Pertanto, sapere se hai un'accelerazione sovrapposta è molto importante.
Per rendere l'interazione dell'utente fluida e il più simile possibile a quella nativa, dobbiamo far funzionare il browser. Idealmente, vogliamo che la CPU del dispositivo mobile configuri l'animazione iniziale, quindi che la GPU sia responsabile solo della composizione di diversi livelli durante il processo di animazione. Questo è ciò che fanno translate3d, scale3d e translateZ: assegnano agli elementi animati il proprio livello, consentendo così al dispositivo di eseguire il rendering di tutto insieme in modo fluido. Per saperne di più sulla composizione accelerata e sul funzionamento di WebKit, Ariya Hidayat ha molte informazioni utili sul suo blog.
Transizioni di pagina
Diamo un'occhiata a tre degli approcci di interazione utente più comuni durante lo sviluppo di un'app web mobile: effetti di scorrimento, capovolgimento e rotazione.
Puoi vedere questo codice in azione qui http://slidfast.appspot.com/slide-flip-rotate.html (nota: questa demo è pensata per un dispositivo mobile, quindi avvia un emulatore, usa il tuo smartphone o tablet oppure riduci le dimensioni della finestra del browser a circa 1024 px o meno).
Per prima cosa, analizzeremo le transizioni di scorrimento, capovolgimento e rotazione e come vengono accelerate. Nota come ogni animazione richieda solo tre o quattro righe di 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 della slide viene richiamata per portare una nuova area di contenuti nella finestra di visualizzazione.
Per l'effetto slide, dichiariamo innanzitutto il 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 come abbiamo questo concetto di pagine di staging a sinistra o a destra. Potrebbe essere in qualsiasi direzione, ma questa è la più comune.
Ora abbiamo l'animazione più l'accelerazione hardware con poche righe di CSS. L'animazione vera e propria si verifica quando scambiamo le classi sugli 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 "soluzione magica".
Quando l'utente fa clic su un elemento di navigazione, eseguiamo il seguente codice JavaScript per scambiare le classi. Non vengono utilizzati framework di terze parti, ma 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 la pagina scorre verso il centro dell'area visibile. Ci affidiamo completamente a CSS3 per il lavoro più pesante.
.stage-left {
left: -480px;
}
.stage-right {
left: 480px;
}
.stage-center {
top: 0;
left: 0;
}
Successivamente, esaminiamo il CSS che gestisce il rilevamento e l'orientamento dei dispositivi mobili. Potremmo indirizzare ogni dispositivo e ogni risoluzione (vedi risoluzione della media query). In questa demo ho utilizzato solo alcuni semplici esempi per coprire la maggior parte delle visualizzazioni 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), è opportuno creare una media query ed escludere l'accelerazione a quel livello. Tieni presente che i trucchi di accelerazione hardware non forniscono alcun miglioramento della velocità in Android Froyo 2.2+. 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, lo scorrimento è noto come scorrimento della pagina. Qui utilizziamo un semplice JavaScript per gestire questo evento su dispositivi iOS e Android (basati su WebKit).
Guarda come funziona http://slidfast.appspot.com/slide-flip-rotate.html.
Quando si ha a che fare con eventi e transizioni touch, la prima cosa da fare è ottenere una gestione della posizione corrente dell'elemento. Per ulteriori informazioni su WebKitCSSMatrix, consulta questo documento.
function pageMove(event) {
// get position after transform
var curTransform = new WebKitCSSMatrix(window.getComputedStyle(page).webkitTransform);
var pagePosition = curTransform.m41;
}
Poiché utilizziamo una transizione di tipo CSS3 ease-out per il cambio pagina, il solito element.offsetLeft non funzionerà.
Successivamente, vogliamo capire in quale direzione l'utente sta scorrendo e impostare una soglia per l'attivazione di un evento (navigazione della 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 anche che misuriamo swipeTime in millisecondi. In questo modo, l'evento di navigazione viene attivato se l'utente scorre rapidamente lo schermo per cambiare pagina.
Per posizionare la pagina e far sembrare le animazioni native mentre un dito tocca lo schermo, utilizziamo le transizioni CSS3 dopo ogni attivazione dell'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 alle transizioni l'aspetto nativo migliore, ma ease-out ha funzionato.
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
Successivamente, esaminiamo l'animazione di rotazione utilizzata in questa demo. In qualsiasi momento, puoi ruotare di 180 gradi la pagina che stai visualizzando per rivelare il retro toccando l'opzione di menu "Contatto". Anche in questo caso, bastano poche righe di CSS e un po' di 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. Purtroppo, anziché ignorare l'inversione, Android fa "capriole" alla pagina ruotandola anziché capovolgerla. Ti consigliamo di utilizzare questa transizione con parsimonia finché 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 esaminato le transizioni di base, vediamo come funzionano e vengono composte.
Per far sì che questa magica sessione di debug si realizzi, 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. Uso un Mac, quindi i comandi potrebbero variare in base al tuo 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
In questo modo Safari viene avviato con un paio di helper di debug. CA_COLOR_OPAQUE ci mostra quali elementi sono effettivamente compositi o accelerati. CA_LOG_MEMORY_USAGE mostra la quantità di memoria che utilizziamo quando inviamo le nostre operazioni di disegno al backing store. In questo modo, puoi sapere esattamente quanto sforzo stai esercitando sul dispositivo mobile e, possibilmente, ricevere suggerimenti su come l'utilizzo della GPU potrebbe scaricare la batteria del dispositivo di destinazione.
Ora avviamo Chrome per visualizzare alcune informazioni sui frame al secondo (FPS):
- Apri il browser web Google Chrome.
- Nella barra degli URL, digita about:flags.
- Scorri verso il basso di alcuni elementi e fai clic su "Attiva" per il contatore FPS.
Se visualizzi questa pagina nella tua versione potenziata di Chrome, vedrai il contatore FPS rosso nell'angolo in alto a sinistra.
In questo modo sappiamo che l'accelerazione hardware è attivata. Inoltre, ci dà un'idea di come viene eseguita l'animazione e se ci sono perdite (animazioni in esecuzione continua che dovrebbero essere interrotte).
Un altro modo per visualizzare l'accelerazione hardware è aprire la stessa pagina in Safari (con le variabili di ambiente che ho menzionato sopra). Ogni elemento DOM accelerato ha una tonalità rossa. In questo modo possiamo vedere 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 del livello di rendering composito".
Un altro ottimo modo per visualizzare i livelli compositi è visualizzare la demo delle foglie che cadono di WebKit mentre questa mod è applicata.
Infine, per comprendere appieno le prestazioni dell'hardware grafico della nostra applicazione, diamo un'occhiata al consumo di memoria. Qui vediamo che stiamo inviando 1,38 MB di istruzioni di disegno ai buffer CoreAnimation su Mac OS. I buffer di memoria di Core Animation sono condivisi tra OpenGL ES e la GPU per creare i pixel finali visualizzati sullo schermo.
Quando ridimensioniamo o ingrandiamo semplicemente la finestra del browser, vediamo anche l'espansione della memoria.
In questo modo puoi farti un'idea di come viene utilizzata la memoria sul tuo dispositivo mobile solo se ridimensioni il browser alle dimensioni corrette. Se esegui il debug o i test per gli ambienti iPhone, ridimensiona a 480 x 320 px. Ora sappiamo esattamente come funziona l'accelerazione hardware e cosa serve per eseguire il debug. Una cosa è leggerlo, ma vedere visivamente i buffer di memoria della GPU in azione è tutta un'altra cosa.
Dietro le quinte: recupero e memorizzazione nella cache
Ora è 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 framework simili, precaricheremo e memorizzeremo nella cache le nostre pagine con chiamate AJAX simultanee.
Affrontiamo alcuni problemi principali del web mobile e i motivi per cui dobbiamo farlo:
- Recupero: il precaricamento delle nostre pagine consente agli utenti di utilizzare l'app offline e di non dover attendere tra le azioni di navigazione. Naturalmente, non vogliamo sovraccaricare la larghezza di banda del dispositivo quando si connette, quindi dobbiamo utilizzare questa funzionalità con parsimonia.
- Memorizzazione nella cache: poi, vogliamo un approccio simultaneo o asincrono per recuperare e memorizzare nella cache queste pagine. Dobbiamo anche utilizzare localStorage (poiché è ben supportato tra i 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 della risposta AJAX e la gestione delle chiamate simultanee. Sfruttiamo anche alcune nuove funzionalità di HTML5 per l'analisi di
xhr.responseText.
Partendo dal codice della demo di scorrimento, capovolgimento e rotazione, iniziamo aggiungendo alcune pagine secondarie e collegandole. Analizzeremo quindi i link e creeremo transizioni al volo.
Guarda la demo di Recupera e memorizza nella cache qui.
Come puoi vedere, qui 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 di dettaglio (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 includo helper o ottimizzazioni nel codice. Qui stiamo semplicemente eseguendo un ciclo iterativo su 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 rivedremo nella sezione successiva quando rileveremo la connessione di rete e determineremo 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 dell'utilizzo di localStorage all'interno di una chiamata AJAX è disponibile in Working Off the Grid with HTML5 Offline. In questo esempio, viene illustrato l'utilizzo di base della memorizzazione nella cache per ogni richiesta e la fornitura degli oggetti memorizzati nella cache quando il server restituisce una risposta diversa da quella 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;
}
}
}
}
Purtroppo, poiché localStorage utilizza UTF-16 per la codifica dei caratteri, ogni singolo byte viene memorizzato come 2 byte, portando il limite di archiviazione da 5 MB a 2,6 MB totali. Il motivo per cui queste pagine/questo markup vengono recuperati e memorizzati nella cache al di fuori dell'ambito della cache dell'applicazione è 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 e espressioni regolari di 3000 righe che rimuovono i tag 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. 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 restrizioni aggiuntive su qualsiasi contenuto ospitato dall'iframe. Il suo valore deve essere un insieme non ordinato di token univoci separati da spazi che non fanno distinzione tra maiuscole e minuscole 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 unica, i moduli e gli script sono disattivati, i link non possono scegliere come target altri contesti di navigazione e i plug-in sono 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 correttamente di spostare implicitamente un nodo da un documento all'altro. Viene generato un errore se il nuovo nodo secondario è stato creato in un documento diverso. Quindi qui usiamo adoptNode e tutto va bene.
Perché utilizzare gli iframe? Perché non utilizzare semplicemente innerHTML? Anche se innerHTML fa ora parte della specifica HTML5, è una pratica pericolosa inserire la risposta di un server (malintenzionato o meno) 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 al suo interno con un fallback di aggiunta solo in caso di eccezione. Anche JQuery Mobile lo utilizza. Tuttavia, non ho eseguito test approfonditi in merito al "malfunzionamento casuale" di innerHTML, ma sarebbe molto interessante vedere tutte le piattaforme interessate. Sarebbe anche interessante vedere quale approccio è più efficace… Ho sentito affermazioni da entrambe le parti anche su questo.
Rilevamento, gestione e profilazione del tipo di rete
Ora che abbiamo la possibilità di memorizzare nel buffer (o nella 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 The Network Information API. Ogni volta che mostro questa funzionalità in una presentazione, qualcuno del pubblico alza la mano e chiede: "A cosa mi serve?". Ecco un modo possibile per configurare un'app web mobile estremamente intelligente.
Iniziamo con uno scenario di buon senso… Quando interagisci con il web da un dispositivo mobile su un treno ad alta velocità, la rete potrebbe non essere disponibile 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 seguente codice copre la maggior parte degli scenari di connessione.
Il seguente codice fornisce:
- Accesso offline tramite
applicationCache. - Rileva se è stato aggiunto ai preferiti e se è offline.
- Rileva il passaggio dalla modalità offline a quella 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);
Nei listener di eventi precedenti, dobbiamo indicare al nostro codice se viene chiamato da un evento o da una richiesta o un aggiornamento di pagina effettivo. Il motivo principale è che l'evento body onload non viene attivato quando si passa dalla modalità online a quella offline.
Poi, abbiamo un semplice controllo per un evento ononline o onload. Questo codice reimposta i link disattivati quando si passa dalla modalità offline a quella 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 manipolerai l'app per la modalità offline e cercherai di recuperare le transazioni in corso in background. 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 alle cose interessanti. Ora che la nostra app sa in quale stato di connessione si trova, possiamo anche controllare il tipo di connessione quando è online e regolarlo di conseguenza. Ho elencato i download e le latenze dei tipici provider nordamericani 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 numerose modifiche al nostro processo fetchAndCache, ma qui ho solo indicato di recuperare le risorse in modo asincrono (true) o sincrono (false) per una determinata connessione.
Cronologia delle richieste Edge (sincrone)
Tempistiche delle richieste (asincrone) relative al Wi-Fi
Ciò consente almeno un metodo di regolazione dell'esperienza utente in base a connessioni lente o veloci. Questa non è in alcun modo una soluzione definitiva. Un'altra attività da svolgere è visualizzare 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. Il punto fondamentale è ridurre le latenze sfruttando tutte le funzionalità della connessione dell'utente con le ultime e migliori offerte di HTML5. Visualizza la demo del rilevamento della rete qui.
Conclusione
Il percorso delle app HTML5 mobile è appena iniziato. Ora vedi le basi molto semplici di un "framework" mobile creato esclusivamente intorno a HTML5 e alle sue tecnologie di supporto. Ritengo che sia importante che gli sviluppatori lavorino su queste funzionalità e le affrontino nel loro nucleo e non mascherate da un wrapper.