Introdução
Atualizações que travam, transições de página instáveis e atrasos periódicos em eventos de toque são apenas algumas das dores de cabeça nos ambientes da Web para dispositivos móveis de hoje. Os desenvolvedores tentam chegar o mais perto possível do nativo, mas muitas vezes são prejudicados por hacks, redefinições e frameworks rígidos.
Neste artigo, vamos discutir o mínimo necessário para criar um app da Web HTML5 para dispositivos móveis. O objetivo principal é desmascarar as complexidades ocultas que os frameworks para dispositivos móveis de hoje tentam esconder. Você vai conhecer uma abordagem minimalista (usando APIs HTML5 principais) e os fundamentos básicos que vão permitir que você escreva seu próprio framework ou contribua para o que você usa atualmente.
Aceleração de hardware
Normalmente, as GPUs lidam com modelagem 3D detalhada ou diagramas CAD, mas, neste caso, queremos que nossos desenhos primitivos (divs, planos de fundo, texto com sombras projetadas, imagens etc.) apareçam e sejam animados de forma suave pela GPU. Infelizmente, a maioria dos desenvolvedores de front-end está delegando esse processo de animação a um framework de terceiros sem se preocupar com a semântica. Mas será que esses recursos principais do CSS3 deveriam ser mascarados? Vou dar alguns motivos para você se importar com isso:
Alocação de memória e carga computacional: se você compor todos os elementos no DOM apenas para fins de aceleração de hardware, a próxima pessoa que trabalhar no seu código pode ficar muito irritada.
Consumo de energia: obviamente, quando o hardware entra em ação, a bateria também é afetada. Ao desenvolver para dispositivos móveis, os desenvolvedores são forçados a considerar uma ampla variedade de restrições de dispositivos ao criar apps da web para dispositivos móveis. Isso será ainda mais comum à medida que os fabricantes de navegadores começarem a permitir o acesso a cada vez mais hardware de dispositivos.
Conflitos: encontrei um comportamento com falhas ao aplicar a aceleração de hardware a partes da página que já estavam aceleradas. Por isso, é muito importante saber se você tem aceleração sobreposta.
Para que a interação do usuário seja tranquila e o mais próxima possível da nativa, precisamos fazer o navegador trabalhar para nós. O ideal é que a CPU do dispositivo móvel configure a animação inicial e que a GPU seja responsável apenas por compor diferentes camadas durante o processo de animação. É isso que translate3d, scale3d e translateZ fazem: eles dão aos elementos animados a própria camada, permitindo que o dispositivo renderize tudo junto de forma suave. Para saber mais sobre a composição acelerada e como o WebKit funciona, Ariya Hidayat tem muitas informações úteis no blog dele.
Transições de páginas
Vamos analisar três das abordagens de interação do usuário mais comuns ao desenvolver um app da web para dispositivos móveis: efeitos de deslizar, virar e girar.
Você pode ver esse código em ação aqui: http://slidfast.appspot.com/slide-flip-rotate.html (observação: essa demonstração foi criada para um dispositivo móvel, então use um emulador, seu smartphone ou tablet ou reduza o tamanho da janela do navegador para ~1024 px ou menos).
Primeiro, vamos analisar as transições de deslizar, virar e girar e como elas são aceleradas. Observe como cada animação usa apenas três ou quatro linhas de CSS e JavaScript.
Deslizante
A mais comum das três abordagens de transição, as transições de página deslizante imitam a sensação nativa dos aplicativos para dispositivos móveis. A transição de slide é invocada para trazer uma nova área de conteúdo para a janela de visualização.
Para o efeito deslizante, primeiro declaramos nossa marcação:
<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>
Observe como temos esse conceito de páginas de preparo à esquerda ou à direita. Pode ser qualquer direção, mas essa é a mais comum.
Agora temos animação e aceleração de hardware com apenas algumas linhas de CSS. A animação real acontece quando trocamos as classes nos elementos div da página.
.page {
position: absolute;
width: 100%;
height: 100%;
/*activate the GPU for compositing each page */
-webkit-transform: translate3d(0, 0, 0);
}
translate3d(0,0,0) é conhecida como a abordagem da "bala de prata".
Quando o usuário clica em um elemento de navegação, executamos o seguinte JavaScript para trocar as classes. Não estamos usando frameworks de terceiros, é JavaScript puro! ;)
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 ou stage-right se torna stage-center e força a página a deslizar para a janela de visualização central. Dependemos totalmente do CSS3 para fazer o trabalho pesado.
.stage-left {
left: -480px;
}
.stage-right {
left: 480px;
}
.stage-center {
top: 0;
left: 0;
}
Em seguida, vamos analisar o CSS que lida com a detecção e a orientação de dispositivos móveis. Poderíamos abordar todos os dispositivos e todas as resoluções (consulte resolução de consulta de mídia). Usei apenas alguns exemplos simples nesta demonstração para cobrir a maioria das visualizações em modo retrato e paisagem em dispositivos móveis. Isso também é útil para aplicar a aceleração de hardware por dispositivo. Por exemplo, como a versão para computador do WebKit acelera todos os elementos transformados (sejam eles 2D ou 3D), faz sentido criar uma consulta de mídia e excluir a aceleração nesse nível. Os truques de aceleração de hardware não melhoram a velocidade no Android Froyo 2.2+. Toda a composição é feita no 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;
}
}
Inversão
Em dispositivos móveis, virar a página é o mesmo que deslizar para fora da tela. Aqui, usamos um JavaScript simples para processar esse evento em dispositivos iOS e Android (com base no WebKit).
Veja em ação http://slidfast.appspot.com/slide-flip-rotate.html.
Ao lidar com eventos de toque e transições, a primeira coisa que você precisa fazer é identificar a posição atual do elemento. Consulte este documento para mais informações sobre WebKitCSSMatrix.
function pageMove(event) {
// get position after transform
var curTransform = new WebKitCSSMatrix(window.getComputedStyle(page).webkitTransform);
var pagePosition = curTransform.m41;
}
Como estamos usando uma transição de desaceleração do CSS3 para a virada de página, o element.offsetLeft comum não vai funcionar.
Em seguida, queremos descobrir em qual direção o usuário está virando e definir um limite para que um evento (navegação na página) ocorra.
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;
}
}
Você também vai notar que estamos medindo o swipeTime em milissegundos. Isso permite que o evento de navegação seja acionado se o usuário deslizar rapidamente pela tela para virar uma página.
Para posicionar a página e fazer com que as animações pareçam nativas enquanto um dedo toca na tela, usamos transições CSS3 após cada disparo de 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';
}
Tentei usar o cubic-bezier para dar a melhor sensação nativa às transições, mas o ease-out foi o suficiente.
Por fim, para que a navegação aconteça, precisamos chamar os métodos slideTo() definidos anteriormente que usamos na última demonstração.
track.ontouchend = function(event) {
pageMove(event);
if (slideDirection == 'left') {
slideTo('products-page');
} else if (slideDirection == 'right') {
slideTo('home-page');
}
}
Em rotação
Agora, vamos conferir a animação de rotação usada nesta demonstração. A qualquer momento, você pode girar a página que está visualizando em 180 graus para revelar o verso tocando na opção de menu "Contato". Novamente, isso requer apenas algumas linhas de CSS e um pouco de JavaScript para atribuir uma classe de transição onclick.
OBSERVAÇÃO: a transição de rotação não é renderizada corretamente na maioria das versões do Android porque não tem recursos de transformação CSS 3D. Infelizmente, em vez de ignorar a inversão, o Android faz a página "girar" em vez de inverter. Recomendamos usar essa transição com moderação até que o suporte melhore.
A marcação (conceito básico de frente e verso):
<div id="front" class="normal">
...
</div>
<div id="back" class="flipped">
<div id="contact-page" class="page">
<h1>Contact Page</h1>
</div>
</div>
O 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;
}
}
O 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);
}
Depurar a aceleração de hardware
Agora que já abordamos as transições básicas, vamos analisar como elas funcionam e são compostas.
Para que essa sessão de depuração mágica aconteça, vamos abrir alguns navegadores e o ambiente de desenvolvimento integrado que você preferir. Primeiro, inicie o Safari na linha de comando para usar algumas variáveis de ambiente de depuração. Estou usando o Mac, então os comandos podem variar de acordo com o SO. Abra o terminal e digite o seguinte:
- $> export CA_COLOR_OPAQUE=1
- $> export CA_LOG_MEMORY_USAGE=1
- $> /Applications/Safari.app/Contents/MacOS/Safari
Isso inicia o Safari com alguns auxiliares de depuração. CA_COLOR_OPAQUE mostra quais elementos são realmente compostos ou acelerados. CA_LOG_MEMORY_USAGE mostra quanta memória estamos usando ao enviar nossas operações de desenho para o armazenamento de backup. Isso informa exatamente o quanto você está sobrecarregando o dispositivo móvel e pode dar dicas de como o uso da GPU pode estar consumindo a bateria do dispositivo de destino.
Agora, vamos abrir o Chrome para ver algumas informações úteis sobre quadros por segundo (QPS):
- Abra o navegador da Web Google Chrome.
- Na barra de URL, digite about:flags.
- Role para baixo alguns itens e clique em "Ativar" para o contador de QPS.
Se você acessar esta página na sua versão turbinada do Chrome, vai encontrar o contador de FPS vermelho no canto superior esquerdo.
É assim que sabemos que a aceleração de hardware está ativada. Ele também dá uma ideia de como a animação é executada e se há vazamentos (animações em execução contínua que precisam ser interrompidas).
Outra maneira de visualizar a aceleração de hardware é abrir a mesma página no Safari (com as variáveis de ambiente que mencionei acima). Cada elemento DOM acelerado tem um tom vermelho. Isso mostra exatamente o que está sendo composto por camada. A navegação branca não está vermelha porque não é acelerada.
Uma configuração semelhante para o Chrome também está disponível em about:flags "Composited render layer borders".
Outra ótima maneira de ver as camadas compostas é conferir a demonstração de folhas caindo do WebKit (link em inglês) enquanto esse mod é aplicado.
Por fim, para entender de verdade o desempenho do hardware gráfico do nosso aplicativo, vamos analisar como a memória está sendo consumida. Aqui, vemos que estamos enviando 1,38 MB de instruções de desenho para os buffers do CoreAnimation no Mac OS. Os buffers de memória do Core Animation são compartilhados entre o OpenGL ES e a GPU para criar os pixels finais que você vê na tela.
Quando simplesmente redimensionamos ou maximizamos a janela do navegador, vemos que a memória também aumenta.
Isso dá uma ideia de como a memória está sendo consumida no seu dispositivo móvel, mas apenas se você redimensionar o navegador para as dimensões corretas. Se você estiver depurando ou testando para ambientes do iPhone, redimensione para 480 x 320 pixels. Agora entendemos exatamente como a aceleração de hardware funciona e o que é necessário para depurar. Uma coisa é ler sobre isso, mas ver os buffers de memória da GPU funcionando visualmente realmente coloca as coisas em perspectiva.
Nos bastidores: busca e armazenamento em cache
Agora é hora de levar o armazenamento em cache de páginas e recursos para o próximo nível. Assim como na abordagem usada pelo JQuery Mobile e frameworks semelhantes, vamos pré-buscar e armazenar em cache nossas páginas com chamadas AJAX simultâneas.
Vamos abordar alguns problemas principais da Web para dispositivos móveis e os motivos para fazer isso:
- Busca: a pré-busca das páginas permite que os usuários usem o app off-line e elimina o tempo de espera entre as ações de navegação. Claro que não queremos sobrecarregar a largura de banda do dispositivo quando ele se conectar, então precisamos usar esse recurso com moderação.
- Armazenamento em cache: em seguida, queremos uma abordagem simultânea ou assíncrona ao buscar e armazenar essas páginas em cache. Também precisamos usar o localStorage (já que ele é bem aceito entre os dispositivos), que infelizmente não é assíncrono.
- AJAX e análise da resposta: usar innerHTML() para inserir a resposta AJAX no DOM é perigoso (e não confiável?). Em vez disso, usamos um mecanismo confiável para inserção de respostas AJAX e processamento de chamadas simultâneas. Também aproveitamos alguns novos recursos do HTML5 para analisar o
xhr.responseText.
Com base no código da demonstração de deslizar, inverter e girar, começamos adicionando algumas páginas secundárias e vinculando-as. Em seguida, vamos analisar os links e criar transições em tempo real.
Confira a demonstração de busca e armazenamento em cache aqui.
Como você pode ver, estamos usando marcação semântica aqui. Apenas um link para outra página. A página filha segue a mesma estrutura de nó/classe da página mãe. Podemos ir além e usar o atributo data-* para nós de "página" etc. E aqui está a página de detalhes (filho) localizada em um arquivo HTML separado (/demo2/home-detail.html) que será carregado, armazenado em cache e configurado para transição no carregamento do 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>
Agora vamos analisar o JavaScript. Para simplificar, não vou incluir auxiliares ou otimizações no código. Aqui, estamos apenas fazendo um loop em uma matriz especificada de nós do DOM para encontrar links que serão buscados e armazenados em cache.
Observação: para esta demonstração, este método fetchAndCache() é chamado no carregamento de página. Vamos reformular isso na próxima seção, quando detectarmos a conexão de rede e determinarmos quando ela deve ser chamada.
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();
}
}
}
};
Garantimos o pós-processamento assíncrono adequado usando o objeto "AJAX". Há uma explicação mais avançada sobre o uso de localStorage em uma chamada AJAX em Working Off the Grid with HTML5 Offline. Neste exemplo, você vê o uso básico do armazenamento em cache em cada solicitação e o fornecimento dos objetos armazenados em cache quando o servidor retorna qualquer resposta que não seja bem-sucedida (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;
}
}
}
}
Infelizmente, como o localStorage usa UTF-16 para codificação de caracteres, cada byte é armazenado como 2 bytes, o que reduz o limite de armazenamento de 5 MB para 2,6 MB no total. O motivo para buscar e armazenar em cache essas páginas/marcações fora do escopo do cache do aplicativo é revelado na próxima seção.
Com os avanços recentes no elemento iframe com HTML5, agora temos uma maneira simples e eficaz de analisar o responseText que recebemos da nossa chamada AJAX. Há muitos analisadores de JavaScript de 3.000 linhas e expressões regulares que removem tags script e assim por diante. Mas por que não deixar o navegador fazer o que ele faz de melhor? Neste exemplo, vamos gravar o responseText em um iframe oculto temporário. Usamos o atributo "sandbox" do HTML5, que desativa scripts e oferece muitos recursos de segurança…
Da especificação: o atributo sandbox, quando especificado, ativa um conjunto de restrições extras em qualquer conteúdo hospedado pelo iframe. O valor precisa ser um conjunto não ordenado de tokens exclusivos separados por espaços que não diferenciam maiúsculas de minúsculas em ASCII. Os valores permitidos são allow-forms, allow-same-origin, allow-scripts e allow-top-navigation. Quando o atributo é definido, o conteúdo é tratado como se fosse de uma origem única, os formulários e scripts são desativados, os links são impedidos de segmentar outros contextos de navegação e os plug-ins são desativados.
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?
}
}
};
O Safari se recusa corretamente a mover um nó de um documento para outro de forma implícita. Um erro será gerado se o novo nó filho tiver sido criado em um documento diferente. Por isso, usamos adoptNode aqui, e tudo funciona bem.
Por que usar iframe? Por que não usar innerHTML? Embora o innerHTML agora faça parte da especificação HTML5, é uma prática perigosa inserir a resposta de um servidor (malicioso ou não) em uma área não verificada. Durante a redação deste artigo, não encontrei ninguém usando nada além de innerHTML. Sei que o JQuery usa isso como base, com um substituto de anexação apenas em caso de exceção. O JQuery Mobile também usa esse recurso. No entanto, não fiz nenhum teste pesado em relação ao innerHTML "parar de funcionar aleatoriamente", mas seria muito interessante ver todas as plataformas afetadas por isso. Também seria interessante ver qual abordagem tem melhor desempenho. Já ouvi argumentos de ambos os lados sobre isso.
Detecção, processamento e criação de perfil do tipo de rede
Agora que temos a capacidade de armazenar em buffer (ou cache preditivo) nosso app da Web, precisamos fornecer os recursos adequados de detecção de conexão que tornam nosso app mais inteligente. É aqui que o desenvolvimento de apps para dispositivos móveis se torna extremamente sensível aos modos on-line/off-line e à velocidade de conexão. Insira a API Network Information. Sempre que mostro esse recurso em uma apresentação, alguém na plateia levanta a mão e pergunta: "Para que eu usaria isso?". Então, aqui está uma maneira de configurar um app da web para dispositivos móveis extremamente inteligente.
Primeiro, um cenário comum e chato… Ao interagir com a Web em um dispositivo móvel em um trem de alta velocidade, a rede pode cair em vários momentos, e diferentes regiões podem ter velocidades de transmissão distintas (por exemplo, HSPA ou 3G podem estar disponíveis em algumas áreas urbanas, mas áreas remotas podem ter tecnologias 2G muito mais lentas). O código a seguir aborda a maioria dos cenários de conexão.
O código a seguir fornece:
- Acesso off-line pelo
applicationCache. - Detecta se o conteúdo está marcado como favorito e off-line.
- Detecta quando o usuário muda de off-line para on-line e vice-versa.
- Detecta conexões lentas e busca conteúdo com base no tipo de rede.
Novamente, todos esses recursos exigem pouquíssimo código. Primeiro, detectamos nossos eventos e cenários de carregamento:
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);
Nos EventListeners acima, precisamos informar ao nosso código se ele está sendo chamado de um evento ou de uma solicitação ou atualização de página real. O principal motivo é que o evento onload do corpo não será acionado ao alternar entre os modos on-line e off-line.
Em seguida, temos uma verificação simples para um evento ononline ou onload. Esse código redefine os links desativados ao alternar do modo off-line para o on-line. No entanto, se o app fosse mais sofisticado, você poderia inserir uma lógica que retomaria a busca de conteúdo ou lidaria com a UX para conexões intermitentes.
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;
}
}
}
O mesmo vale para processOffline(). Aqui, você manipularia o app para o modo off-line e tentaria recuperar as transações que estavam acontecendo em segundo plano. O código abaixo encontra todos os links externos e os desativa, prendendo os usuários no nosso app off-line PARA 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);
}
}
}
Agora vamos para a parte boa. Agora que o app sabe o estado de conexão, também podemos verificar o tipo de conexão quando ele está on-line e fazer os ajustes necessários. Listei as velocidades de download e as latências típicas dos provedores norte-americanos nos comentários de cada conexão.
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);
}
}
Há vários ajustes que poderíamos fazer no processo fetchAndCache, mas tudo o que fiz aqui foi dizer para ele buscar os recursos de forma assíncrona (true) ou síncrona (false) para uma determinada conexão.
Linha do tempo de solicitações do Edge (síncronas)
Linha do tempo de solicitação de Wi-Fi (assíncrona)
Isso permite pelo menos algum método de ajuste da experiência do usuário com base em conexões lentas ou rápidas. Essa não é uma solução definitiva. Outra tarefa seria mostrar um modal de carregamento quando um link é clicado (em conexões lentas) enquanto o app ainda está buscando a página desse link em segundo plano. O principal objetivo é reduzir as latências e, ao mesmo tempo, aproveitar ao máximo os recursos da conexão do usuário com o que há de mais recente e melhor no HTML5. Confira a demonstração da detecção de rede aqui.
Conclusão
A jornada dos apps HTML5 para dispositivos móveis está apenas começando. Agora você já conhece os fundamentos básicos de um framework para dispositivos móveis criado apenas com HTML5 e as tecnologias compatíveis. Acho importante que os desenvolvedores trabalhem e lidem com esses recursos em sua essência, sem mascará-los com um wrapper.