Introducción
Las actualizaciones que tardan en cargar, las transiciones de página entrecortadas y las demoras periódicas en los eventos de presión son solo algunos de los problemas que se presentan en los entornos web para dispositivos móviles actuales. Los desarrolladores intentan acercarse lo más posible a la experiencia nativa, pero a menudo se ven frustrados por hackeos, restablecimientos y marcos rígidos.
En este artículo, analizaremos lo mínimo necesario para crear una app web en HTML5 para dispositivos móviles. El objetivo principal es revelar las complejidades ocultas que los frameworks para dispositivos móviles actuales intentan ocultar. Verás un enfoque minimalista (con las APIs principales de HTML5) y los fundamentos básicos que te permitirán escribir tu propio framework o contribuir al que usas actualmente.
Aceleración de hardware
Normalmente, las GPUs se encargan del modelado 3D detallado o los diagramas CAD, pero, en este caso, queremos que nuestros dibujos primitivos (divs, fondos, texto con sombras paralelas, imágenes, etcétera) se vean y se animen de forma fluida a través de la GPU. Lo lamentable es que la mayoría de los desarrolladores de frontend delegan este proceso de animación en un framework de terceros sin preocuparse por la semántica, pero ¿deberían enmascararse estas funciones principales de CSS3? Te daré algunas razones por las que es importante preocuparse por estas cosas:
Asignación de memoria y carga computacional: Si compones cada elemento del DOM solo para acelerar el hardware, es posible que la próxima persona que trabaje en tu código te persiga y te golpee.
Consumo de energía: Obviamente, cuando se activa el hardware, también se activa la batería. Cuando desarrollan para dispositivos móviles, los desarrolladores se ven obligados a tener en cuenta la amplia variedad de restricciones de los dispositivos mientras escriben apps web para dispositivos móviles. Esto será aún más frecuente a medida que los fabricantes de navegadores comiencen a habilitar el acceso a más y más hardware de dispositivos.
Conflictos: Experimenté un comportamiento defectuoso cuando apliqué la aceleración de hardware a partes de la página que ya estaban aceleradas. Por lo tanto, saber si tienes una aceleración superpuesta es muy importante.
Para que la interacción del usuario sea fluida y lo más cercana posible a la nativa, debemos hacer que el navegador funcione para nosotros. Lo ideal es que la CPU del dispositivo móvil configure la animación inicial y, luego, que la GPU se encargue solo de componer diferentes capas durante el proceso de animación. Esto es lo que hacen translate3d, scale3d y translateZ: les dan a los elementos animados su propia capa, lo que permite que el dispositivo renderice todo junto sin problemas. Para obtener más información sobre la composición acelerada y cómo funciona WebKit, Ariya Hidayat tiene mucha información útil en su blog.
Transiciones de página
Veamos tres de los enfoques de interacción del usuario más comunes cuando se desarrolla una app web para dispositivos móviles: efectos de deslizamiento, volteo y rotación.
Puedes ver este código en acción aquí http://slidfast.appspot.com/slide-flip-rotate.html (nota: Esta demostración se creó para un dispositivo móvil, así que inicia un emulador, usa tu teléfono o tablet, o reduce el tamaño de la ventana del navegador a ~1,024 px o menos).
Primero, analizaremos las transiciones de diapositivas, giros y rotaciones, y cómo se aceleran. Observa cómo cada animación solo requiere tres o cuatro líneas de CSS y JavaScript.
Variable
Las transiciones de página deslizantes, el más común de los tres enfoques de transición, imitan la sensación nativa de las aplicaciones para dispositivos móviles. Se invoca la transición de diapositiva para mostrar un área de contenido nueva en el viewport.
Para el efecto de diapositiva, primero declaramos nuestro lenguaje de marcado:
<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>
Observa cómo tenemos este concepto de páginas de preparación a la izquierda o a la derecha. Básicamente, puede ser en cualquier dirección, pero esta es la más común.
Ahora tenemos animación y aceleración por hardware con solo unas pocas líneas de CSS. La animación real se produce cuando intercambiamos clases en los elementos div de la 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) se conoce como el enfoque de “bala de plata”.
Cuando el usuario hace clic en un elemento de navegación, ejecutamos el siguiente código JavaScript para intercambiar las clases. No se usan frameworks de terceros, 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 se convierte en stage-center y obliga a la página a deslizarse hacia el centro del viewport. Dependemos completamente de CSS3 para hacer el trabajo pesado.
.stage-left {
left: -480px;
}
.stage-right {
left: 480px;
}
.stage-center {
top: 0;
left: 0;
}
A continuación, veamos el código CSS que controla la detección y la orientación de los dispositivos móviles. Podríamos abordar cada dispositivo y cada resolución (consulta resolución de consultas de medios). En esta demostración, usé solo algunos ejemplos sencillos para abarcar la mayoría de las vistas verticales y horizontales en dispositivos móviles. Esto también es útil para aplicar la aceleración de hardware por dispositivo. Por ejemplo, dado que la versión para computadoras de escritorio de WebKit acelera todos los elementos transformados (independientemente de si son en 2D o 3D), tiene sentido crear una consulta de medios y excluir la aceleración en ese nivel. Ten en cuenta que los trucos de aceleración de hardware no proporcionan ninguna mejora de velocidad en Android Froyo 2.2 o versiones posteriores. Toda la composición se realiza dentro 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;
}
}
Giro
En los dispositivos móviles, el volteo se conoce como deslizar la página. Aquí usamos un código JavaScript simple para controlar este evento en dispositivos iOS y Android (basados en WebKit).
Mírala en acción http://slidfast.appspot.com/slide-flip-rotate.html.
Cuando trabajes con eventos táctiles y transiciones, lo primero que querrás hacer es controlar la posición actual del elemento. Consulta este documento para obtener más información sobre WebKitCSSMatrix.
function pageMove(event) {
// get position after transform
var curTransform = new WebKitCSSMatrix(window.getComputedStyle(page).webkitTransform);
var pagePosition = curTransform.m41;
}
Dado que usamos una transición de aceleración CSS3 para el cambio de página, el element.offsetLeft habitual no funcionará.
A continuación, queremos determinar en qué dirección el usuario está deslizando el dedo y establecer un umbral para que se produzca un evento (navegación de página).
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;
}
}
También notarás que medimos el swipeTime en milisegundos. Esto permite que se active el evento de navegación si el usuario desliza rápidamente la pantalla para cambiar de página.
Para posicionar la página y hacer que las animaciones parezcan nativas mientras un dedo toca la pantalla, usamos transiciones CSS3 después de cada activación de eventos.
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';
}
Intenté jugar con cubic-bezier para que las transiciones se sintieran lo más nativas posible, pero ease-out funcionó.
Por último, para que se produzca la navegación, debemos llamar a los métodos slideTo() definidos anteriormente que usamos en la última demostración.
track.ontouchend = function(event) {
pageMove(event);
if (slideDirection == 'left') {
slideTo('products-page');
} else if (slideDirection == 'right') {
slideTo('home-page');
}
}
Cómo girar el clip
A continuación, veamos la animación de rotación que se usa en esta demostración. En cualquier momento, puedes rotar 180 grados la página que estás viendo para mostrar el reverso. Para ello, presiona la opción de menú “Contacto”. Nuevamente, esto solo requiere unas pocas líneas de CSS y algo de JavaScript para asignar una clase de transición onclick.
NOTA: La transición de rotación no se renderiza correctamente en la mayoría de las versiones de Android porque carece de capacidades de transformación CSS en 3D. Desafortunadamente, en lugar de ignorar el cambio, Android hace que la página se aleje "como una rueda" rotando en lugar de cambiar. Te recomendamos que uses esta transición con moderación hasta que mejore la compatibilidad.
El margen (concepto básico de la parte delantera y trasera):
<div id="front" class="normal">
...
</div>
<div id="back" class="flipped">
<div id="contact-page" class="page">
<h1>Contact Page</h1>
</div>
</div>
El código 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;
}
}
El 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);
}
Cómo depurar la aceleración de hardware
Ahora que ya vimos las transiciones básicas, veamos cómo funcionan y se componen.
Para que esta sesión de depuración mágica se lleve a cabo, iniciemos un par de navegadores y el IDE que prefieras. Primero, inicia Safari desde la línea de comandos para usar algunas variables de entorno de depuración. Uso Mac, por lo que los comandos pueden variar según tu SO. Abre la terminal y escribe lo siguiente:
- $> export CA_COLOR_OPAQUE=1
- $> export CA_LOG_MEMORY_USAGE=1
- $> /Applications/Safari.app/Contents/MacOS/Safari
Esto inicia Safari con un par de asistentes de depuración. CA_COLOR_OPAQUE nos muestra qué elementos se componen o aceleran realmente. CA_LOG_MEMORY_USAGE nos muestra cuánta memoria usamos cuando enviamos nuestras operaciones de dibujo al almacén de respaldo. Esto te indica exactamente cuánta tensión le estás aplicando al dispositivo móvil y, posiblemente, te da pistas sobre cómo el uso de la GPU podría estar agotando la batería del dispositivo objetivo.
Ahora, iniciemos Chrome para ver información sobre los fotogramas por segundo (FPS):
- Abre el navegador web Google Chrome.
- En la barra de URL, escribe about:flags.
- Desplázate hacia abajo unos elementos y haz clic en “Habilitar” para el contador de FPS.
Si ves esta página en tu versión mejorada de Chrome, verás el contador de FPS rojo en la esquina superior izquierda.
Así es como sabemos que la aceleración de hardware está activada. También nos da una idea de cómo se ejecuta la animación y si tienes alguna fuga (animaciones en ejecución continua que deberían detenerse).
Otra forma de visualizar la aceleración por hardware es abrir la misma página en Safari (con las variables de entorno que mencioné anteriormente). Todos los elementos del DOM acelerados tienen un tinte rojo. Esto nos muestra exactamente qué se compone por capa. Observa que la navegación blanca no es roja porque no está acelerada.
También hay un parámetro de configuración similar para Chrome disponible en “Composited render layer borders” de about:flags.
Otra excelente forma de ver las capas compuestas es mirar la demostración de hojas que caen de WebKit mientras se aplica esta modificación.
Por último, para comprender realmente el rendimiento del hardware de gráficos de nuestra aplicación, veamos cómo se consume la memoria. Aquí vemos que enviamos 1.38 MB de instrucciones de dibujo a los búferes de CoreAnimation en macOS. Los búferes de memoria de Core Animation se comparten entre OpenGL ES y la GPU para crear los píxeles finales que ves en la pantalla.
Cuando simplemente cambiamos el tamaño o maximizamos la ventana del navegador, también vemos que la memoria se expande.
Esto te da una idea de cómo se consume la memoria en tu dispositivo móvil solo si cambias el tamaño del navegador a las dimensiones correctas. Si estabas depurando o probando entornos de iPhone, cambia el tamaño a 480 px por 320 px. Ahora entendemos exactamente cómo funciona la aceleración por hardware y qué se necesita para depurar. Una cosa es leer sobre el tema, pero ver visualmente cómo funcionan los búferes de memoria de la GPU realmente ayuda a comprenderlo mejor.
Detrás de escena: Recuperación y almacenamiento en caché
Ahora es el momento de llevar el almacenamiento en caché de páginas y recursos al siguiente nivel. Al igual que el enfoque que usan JQuery Mobile y otros frameworks similares, vamos a recuperar previamente y almacenar en caché nuestras páginas con llamadas AJAX simultáneas.
Abordemos algunos problemas centrales de la Web para dispositivos móviles y los motivos por los que debemos hacerlo:
- Búsqueda previa: La búsqueda previa de nuestras páginas permite que los usuarios usen la app sin conexión y que no haya esperas entre las acciones de navegación. Por supuesto, no queremos saturar el ancho de banda del dispositivo cuando se conecta, por lo que debemos usar esta función con moderación.
- Almacenamiento en caché: A continuación, queremos un enfoque simultáneo o asíncrono cuando recuperamos y almacenamos en caché estas páginas. También necesitamos usar localStorage (ya que es compatible con muchos dispositivos), que, lamentablemente, no es asíncrono.
- AJAX y análisis de la respuesta: Usar innerHTML() para insertar la respuesta de AJAX en el DOM es peligroso (¿y poco confiable?). En su lugar, usamos un mecanismo confiable para la inserción de respuestas de AJAX y el control de llamadas simultáneas. También aprovechamos algunas funciones nuevas de HTML5 para analizar el elemento
xhr.responseText.
Partiendo del código de la demostración de deslizar, voltear y rotar, comenzamos por agregar algunas páginas secundarias y vincularlas. Luego, analizaremos los vínculos y crearemos transiciones sobre la marcha.
Mira la demostración de Fetch and Cache aquí.
Como puedes ver, aquí aprovechamos el lenguaje de marcado semántico. Solo un vínculo a otra página. La página secundaria sigue la misma estructura de nodos o clases que su página principal. Podríamos ir un paso más allá y usar el atributo data-* para los nodos de “página”, etcétera. Y aquí está la página de detalles (secundaria) ubicada en un archivo HTML independiente (/demo2/home-detail.html) que se cargará, almacenará en caché y configurará para la transición en la carga de la 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>
Ahora, veamos el código JavaScript. Para simplificar, no incluiré ninguna función auxiliar ni optimización en el código. Aquí, lo único que hacemos es iterar un array especificado de nodos del DOM para extraer vínculos que se recuperarán y almacenarán en caché.
Nota: Para esta demostración, se llama al método fetchAndCache() cuando se carga la página. La volveremos a trabajar en la siguiente sección cuando detectemos la conexión de red y determinemos cuándo se debe llamar.
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();
}
}
}
};
Garantizamos el procesamiento posterior asíncrono adecuado a través del uso del objeto "AJAX". En Trabajar sin conexión con HTML5, se ofrece una explicación más avanzada sobre el uso de localStorage en una llamada de AJAX. En este ejemplo, se muestra el uso básico del almacenamiento en caché en cada solicitud y cómo se proporcionan los objetos almacenados en caché cuando el servidor devuelve cualquier respuesta que no sea exitosa (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;
}
}
}
}
Lamentablemente, como localStorage usa UTF-16 para la codificación de caracteres, cada byte se almacena como 2 bytes, lo que reduce nuestro límite de almacenamiento de 5 MB a 2.6 MB en total. En la siguiente sección, se explica el motivo por el que se recuperan y almacenan en caché estas páginas o este lenguaje de marcado fuera del alcance de la caché de la aplicación.
Con los avances recientes en el elemento iframe con HTML5, ahora tenemos una forma simple y eficaz de analizar el responseText que obtenemos de nuestra llamada a AJAX. Hay muchos analizadores de JavaScript y expresiones regulares de 3,000 líneas que quitan etiquetas de secuencias de comandos, etcétera. Pero, ¿por qué no dejar que el navegador haga lo que mejor sabe hacer? En este ejemplo, escribiremos el responseText en un iframe oculto temporal. Usamos el atributo “sandbox” de HTML5, que inhabilita las secuencias de comandos y ofrece muchas funciones de seguridad…
De la especificación: El atributo sandbox, cuando se especifica, habilita un conjunto de restricciones adicionales en cualquier contenido alojado por el iframe. Su valor debe ser un conjunto no ordenado de tokens únicos separados por espacios que no distinguen mayúsculas de minúsculas en ASCII. Los valores permitidos son allow-forms, allow-same-origin, allow-scripts y allow-top-navigation. Cuando se configura el atributo, el contenido se considera como proveniente de un origen único, se inhabilitan los formularios y las secuencias de comandos, se impide que los vínculos segmenten otros contextos de navegación y se inhabilitan los complementos.
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 rechaza correctamente mover de forma implícita un nodo de un documento a otro. Se genera un error si el nuevo nodo secundario se creó en un documento diferente. Aquí usamos adoptNode y todo está bien.
Entonces, ¿por qué usar iframes? ¿Por qué no usar solo innerHTML? Aunque innerHTML ahora forma parte de la especificación de HTML5, es una práctica peligrosa insertar la respuesta de un servidor (malo o bueno) en un área sin verificar. Mientras escribía este artículo, no encontré a nadie que usara algo más que innerHTML. Sé que JQuery lo usa en su núcleo con una alternativa de anexar solo en caso de excepción. Y JQuery Mobile también lo usa. Sin embargo, no realicé pruebas exhaustivas con respecto a que innerHTML “deja de funcionar de forma aleatoria”, pero sería muy interesante ver todas las plataformas a las que afecta. También sería interesante ver qué enfoque tiene un mejor rendimiento… También escuché afirmaciones de ambos lados sobre esto.
Detección, control y generación de perfiles del tipo de red
Ahora que podemos almacenar en búfer (o en caché predictiva) nuestra app web, debemos proporcionar las funciones de detección de conexión adecuadas que hagan que nuestra app sea más inteligente. Aquí es donde el desarrollo de apps para dispositivos móviles se vuelve extremadamente sensible a los modos en línea y sin conexión, y a la velocidad de conexión. Ingresa a The Network Information API. Cada vez que muestro esta función en una presentación, alguien del público levanta la mano y pregunta: "¿Para qué usaría eso?". Por lo tanto, aquí tienes una posible forma de configurar una app web para dispositivos móviles extremadamente inteligente.
Primero, un escenario aburrido de sentido común… Mientras interactúas con la Web desde un dispositivo móvil en un tren de alta velocidad, es posible que la red desaparezca en varios momentos y que diferentes ubicaciones geográficas admitan diferentes velocidades de transmisión (p.ej., Es posible que HSPA o 3G estén disponibles en algunas áreas urbanas, pero las áreas remotas podrían admitir tecnologías 2G mucho más lentas. El siguiente código aborda la mayoría de las situaciones de conexión.
El siguiente código proporciona lo siguiente:
- Acceso sin conexión a través de
applicationCache - Detecta si el lugar está marcado como favorito y disponible sin conexión.
- Detecta cuando se cambia de sin conexión a en línea y viceversa.
- Detecta conexiones lentas y recupera contenido según el tipo de red.
Nuevamente, todas estas funciones requieren muy poco código. Primero, detectamos nuestros eventos y situaciones de carga:
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);
En los EventListeners anteriores, debemos indicarle a nuestro código si se lo llama desde un evento o desde una solicitud o actualización de página real. El motivo principal es que el evento onload del cuerpo no se activará cuando se cambie entre los modos en línea y sin conexión.
A continuación, tenemos una verificación simple para un evento ononline o onload. Este código restablece los vínculos inhabilitados cuando se cambia de sin conexión a en línea, pero, si esta app fuera más sofisticada, podrías insertar lógica que reanude la recuperación de contenido o controle la UX para conexiones 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;
}
}
}
Lo mismo sucede con processOffline(). Aquí manipularías tu app para el modo sin conexión y tratarías de recuperar las transacciones que se estaban realizando en segundo plano. El siguiente código extrae todos nuestros vínculos externos y los inhabilita, lo que atrapa a los usuarios en nuestra app sin conexión PARA SIEMPRE, ¡muajajajaja!
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);
}
}
}
Bien, pasemos a lo interesante. Ahora que nuestra app sabe en qué estado de conexión se encuentra, también podemos verificar el tipo de conexión cuando está en línea y realizar los ajustes necesarios. En los comentarios de cada conexión, enumeré las latencias y las velocidades de descarga típicas de los proveedores de América del Norte.
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);
}
}
Podríamos realizar varios ajustes en nuestro proceso fetchAndCache, pero lo único que hice aquí fue indicarle que recupere los recursos de forma asíncrona (verdadero) o síncrona (falso) para una conexión determinada.
Cronograma de solicitudes de Edge (síncronas)
Cronograma de solicitudes de Wi-Fi (asíncronas)
Esto permite al menos algún método de ajuste de la experiencia del usuario en función de las conexiones lentas o rápidas. Esta no es, de ninguna manera, una solución definitiva. Otra tarea pendiente sería mostrar un modal de carga cuando se haga clic en un vínculo (en conexiones lentas) mientras la app aún puede estar recuperando la página de ese vínculo en segundo plano. El punto importante aquí es reducir las latencias y, al mismo tiempo, aprovechar al máximo las capacidades de la conexión del usuario con lo mejor y más reciente que ofrece HTML5. Mira la demostración de detección de redes aquí.
Conclusión
El viaje por el camino de las apps HTML5 para dispositivos móviles recién comienza. Ahora puedes ver los fundamentos muy simples y básicos de un "framework" para dispositivos móviles creado únicamente con HTML5 y sus tecnologías de asistencia. Creo que es importante que los desarrolladores trabajen con estas funciones y las aborden en su núcleo, y no enmascaradas por un wrapper.