Introducción
Las actualizaciones que tardan en cargarse, las transiciones de página entrecortadas y los retrasos periódicos en los eventos de presión son solo algunos de los dolores de cabeza que se presentan en los entornos de la Web móvil de hoy en día. Los desarrolladores intentan acercarse lo más posible a lo nativo, pero a menudo se ven desviados por hackeos, restablecimientos y frameworks rígidos.
En este artículo, analizaremos los requisitos mínimos para crear una app web 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 GPU controlan el 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.) aparezcan de forma fluida 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 a un framework de terceros sin preocuparse por la semántica, pero ¿deberían ocultarse estas funciones principales de CSS3? Te daré algunas razones por las que es importante preocuparse por esto:
Asignación de memoria y carga de procesamiento: Si compones cada elemento del DOM solo para la aceleración de hardware, la próxima persona que trabaje en tu código puede perseguirte y golpearte con severidad.
Consumo de energía: Obviamente, cuando el hardware se activa, también lo hace 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 creadores de navegadores comiencen a habilitar el acceso a cada vez más hardware de dispositivos.
Conflictos: Encontré un comportamiento con errores cuando apliqué la aceleración de hardware a partes de la página que ya estaban aceleradas. Por lo tanto, saber si tienes 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 las 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: los 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, por lo que debes iniciar un emulador, usar tu teléfono o tablet, o reducir el tamaño de la ventana del navegador a ~1024 px o menos).
Primero, analizaremos las transiciones de deslizamiento, volteo y rotación, 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 que aparezca un área de contenido nueva en el viewport.
Para el efecto de deslizamiento, 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 etapa de pruebas a la izquierda o a la derecha. En esencia, podría ser cualquier dirección, pero esta es la más común.
Ahora tenemos animación y aceleración de hardware con solo unas pocas líneas de CSS. La animación real ocurre 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 la “solución mágica”.
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, ¡esto es 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 o stage-right se convierte en stage-center y fuerza a la página a deslizarse hacia el viewport central. Dependemos por completo de CSS3 para que haga el trabajo pesado.
.stage-left {
left: -480px;
}
.stage-right {
left: 480px;
}
.stage-center {
top: 0;
left: 0;
}
A continuación, veamos el 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 consulta 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 WebKit acelera todos los elementos transformados (sin importar si son 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 y 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, pasar de página se conoce como deslizar el dedo por la página. Aquí usamos un código JavaScript simple para controlar este evento en dispositivos iOS y Android (basados en WebKit).
Míralo en acción en http://slidfast.appspot.com/slide-flip-rotate.html.
Cuando trabajes con eventos táctiles y transiciones, lo primero que querrás hacer es obtener un controlador de 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 desaceleración de CSS3 para el cambio de página, el element.offsetLeft habitual no funcionará.
A continuación, queremos averiguar en qué dirección se desliza el usuario y establecer un umbral para que se produzca un evento (navegación de páginas).
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 el dedo rápidamente por la pantalla para pasar de página.
Para posicionar la página y hacer que las animaciones se vean nativas mientras un dedo toca la pantalla, usamos transiciones de CSS3 después de que se activa cada 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';
}
Intenté experimentar con cubic-bezier para dar la mejor sensación nativa a las transiciones, pero ease-out funcionó.
Por último, para que se produzca la navegación, debemos llamar a los métodos slideTo() que definimos antes y 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');
}
}
En rotación
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 de CSS en 3D. Lamentablemente, en lugar de ignorar el giro, Android hace que la página "dé una voltereta" rotando en lugar de girar. Te recomendamos que uses esta transición con moderación hasta que mejore la compatibilidad.
El lenguaje de marcado (concepto básico de anverso y reverso):
<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 cómo se componen.
Para que esta sesión de depuración mágica se lleve a cabo, iniciaremos 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. Estoy en 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 ayudantes de depuración. CA_COLOR_OPAQUE nos muestra qué elementos se componen o aceleran realmente. CA_LOG_MEMORY_USAGE nos muestra cuánta memoria estamos usando cuando enviamos nuestras operaciones de dibujo al almacén de respaldo. Esto te indica exactamente cuánta tensión estás ejerciendo en el dispositivo móvil y, posiblemente, te dé pistas sobre cómo el uso de la GPU podría estar agotando la batería del dispositivo de destino.
Ahora, iniciemos Chrome para ver información útil sobre los fotogramas por segundo (FPS):
- Abre el navegador web Google Chrome.
- En la barra de URL, escribe about:flags.
- Desplázate hacia abajo por algunos 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 que se ejecutan de forma continua y que se deben detener).
Otra forma de visualizar la aceleración de hardware es abrir la misma página en Safari (con las variables de entorno que mencioné anteriormente). Cada elemento del DOM acelerado tiene un tono rojo. Esto nos muestra exactamente qué se está componiendo 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 en about:flags, “Bordes de la capa de renderización compuesta”.
Otra excelente manera de ver las capas compuestas es ver la demostración de hojas que caen de WebKit mientras se aplica este mod.
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 estamos enviando 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, vemos que la memoria también 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 estuvieras depurando o probando para entornos de iPhone, cambia el tamaño a 480 px por 320 px. Ahora entendemos exactamente cómo funciona la aceleración de hardware y qué se necesita para depurar. Una cosa es leer sobre el tema, pero ver cómo funcionan visualmente los búferes de memoria de la GPU realmente pone las cosas en perspectiva.
Tras bambalinas: Recuperación y almacenamiento en caché
Ahora es el momento de llevar el almacenamiento en caché de páginas y recursos al siguiente nivel. De manera similar al enfoque que usan JQuery Mobile y frameworks similares, recuperaremos previamente y almacenaremos en caché nuestras páginas con llamadas AJAX simultáneas.
Abordemos algunos problemas principales de la Web móvil y los motivos por los que debemos hacerlo:
- Recuperación: La recuperación 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é: Luego, queremos un enfoque simultáneo o asíncrono cuando se recuperen y almacenen en caché estas páginas. También necesitamos usar localStorage (ya que es compatible con los 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 manejo de llamadas simultáneas. También aprovechamos algunas funciones nuevas de HTML5 para analizar el
xhr.responseText.
A partir del código de la demostración de deslizamiento, volteo y rotación, comenzamos agregando algunas páginas secundarias y vinculándolas. Luego, analizaremos los vínculos y crearemos transiciones sobre la marcha.
Mira la demostración de Recuperar y almacenar en caché aquí.
Como puedes ver, aquí estamos aprovechando el lenguaje de marcado semántico. Solo un vínculo a otra página. La página secundaria sigue la misma estructura de nodos y clases que la principal. Podríamos ir un paso más allá y usar el atributo data-* para los nodos de “página”, etc. Y aquí está la página de detalles (secundaria) ubicada en un archivo HTML separado (/demo2/home-detail.html) que se cargará, se almacenará en caché y se configurará para la transición cuando se cargue 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, dejaré fuera del código cualquier ayudante u optimización. Aquí, lo único que hacemos es recorrer un array especificado de nodos del DOM para encontrar vínculos que se puedan recuperar y almacenar en caché.
Nota: En esta demostración, se llama al método fetchAndCache() cuando se carga la página. Lo modificaremos 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 mediante el uso del objeto “AJAX”. Hay una explicación más avanzada sobre el uso de localStorage dentro de una llamada AJAX en Working Off the Grid with HTML5 Offline. 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 correcta (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 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 sencilla y eficaz de analizar el responseText que obtenemos de nuestra llamada AJAX. Hay muchos analizadores de JavaScript de 3,000 líneas y expresiones regulares que quitan las etiquetas de secuencia de comandos, entre otras cosas. 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: Cuando se especifica, el atributo sandbox habilita un conjunto de restricciones adicionales en cualquier contenido alojado por el iframe. Su valor debe ser un conjunto desordenado de tokens únicos separados por espacios que no distingan mayúsculas de minúsculas en ASCII. Los valores permitidos son allow-forms, allow-same-origin, allow-scripts y allow-top-navigation. Cuando se establece el atributo, el contenido se trata como si fuera de un origen único, se inhabilitan los formularios y las secuencias de comandos, se impide que los vínculos se orienten a 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. Por lo tanto, aquí usamos adoptNode y todo está bien.
¿Por qué usar un iframe? ¿Por qué no usar simplemente innerHTML? Aunque innerHTML ahora forma parte de la especificación de HTML5, es una práctica peligrosa insertar la respuesta de un servidor (bueno o malo) en un área no verificada. Durante la redacción de este artículo, no pude encontrar a nadie que usara otra cosa que no fuera innerHTML. Sé que JQuery lo usa en su núcleo con un resguardo de anexión solo en caso de excepción. Y jQuery Mobile también lo usa. Sin embargo, no he realizado pruebas exhaustivas con respecto a que innerHTML “deje 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… He escuchado argumentos de ambas partes sobre esto.
Detección, control y generación de perfiles de tipos de red
Ahora que tenemos la capacidad de almacenar en búfer (o almacenar en caché de forma 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 API de Network Information. 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 muy probable que la red se desconecte en varios momentos y que diferentes ubicaciones geográficas admitan diferentes velocidades de transmisión (p.ej., HSPA o 3G podrían estar 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 se marcó como favorito y si está sin conexión.
- Detecta cuándo 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 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 con conexión, 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 ocurre 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 busca todos nuestros vínculos externos y los inhabilita, lo que atrapa a los usuarios en nuestra app sin conexión PARA SIEMPRE, ¡muajajaja!
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 bueno. 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 ajustarla según corresponda. En los comentarios de cada conexión, se indican las velocidades de descarga y las latencias típicas de los proveedores de Norteamérica.
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 hacer muchos ajustes en nuestro proceso fetchAndCache, pero lo único que hice aquí fue indicarle que recuperara los recursos de forma asíncrona (true) o síncrona (false) para una conexión determinada.
Cronograma de solicitudes perimetrales (síncronas)
Cronograma de solicitudes de WIFI (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. De ninguna manera es una solución definitiva. Otra tarea pendiente sería mostrar un modal de carga cuando se hace 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 principal aquí es reducir las latencias y, al mismo tiempo, aprovechar todas las capacidades de la conexión del usuario con lo último y lo mejor que ofrece HTML5. Mira la demostración de detección de red aquí.
Conclusión
El recorrido por el camino de las apps HTML5 para dispositivos móviles recién comienza. Ahora puedes ver los fundamentos básicos y sencillos de un “framework” para dispositivos móviles creado únicamente en torno a HTML5 y sus tecnologías compatibles. Creo que es importante que los desarrolladores trabajen con estas funciones y las aborden en su esencia, sin ocultarlas con un wrapper.