Técnicas de HTML5 para optimizar el rendimiento en dispositivos móviles

Wesley Hales
Wesley Hales

Introducción

Las actualizaciones rotativas, las transiciones de páginas entrecortadas y las demoras periódicas en los eventos de presión son solo algunos de los dolores de cabeza en los entornos web móviles actuales. Los desarrolladores intentan acercarse lo más posible a los anuncios nativos, pero, a menudo, se desvían por hackeos, restablecimientos y frameworks rígidos.

En este artículo, analizaremos lo mínimo que se necesita para crear una aplicación web HTML5 para dispositivos móviles. El objetivo principal es desenmascarar las complejidades ocultas que los frameworks móviles actuales intentan ocultar. Verás un enfoque minimalista (con las principales APIs de HTML5) y aspectos básicos básicos que te permitirán escribir tu propio marco de trabajo o contribuir con el que usas actualmente.

Aceleración de hardware

Normalmente, las GPU manejan modelado 3D detallado o diagramas CAD, pero en este caso, queremos que nuestros dibujos primitivos (divs, fondos, texto con sombras paralelas, imágenes, etc.) se vean fluidos y se animen sin problemas a través de la GPU. Lo lamentable es que la mayoría de los desarrolladores front-end ofrecen este proceso de animación en un framework de terceros sin preocuparse por la semántica, pero ¿deben enmascararse estas funciones principales de CSS3? Te explicaré algunos motivos por los que prestar atención a este contenido es importante:

  1. Asignación de memoria y carga computacional: si optas por componer cada elemento en el DOM solo para acelerar el hardware, la siguiente persona que trabaje en tu código puede perseguirte y vencerte considerablemente.

  2. Consumo de energía: Obviamente, cuando entra el hardware, también lo hace la batería. Al desarrollar aplicaciones para dispositivos móviles, los desarrolladores se ven obligados a tener en cuenta la amplia variedad de restricciones de dispositivos cuando escriben aplicaciones 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 del dispositivo.

  3. Conflictos: Tuve un comportamiento de fallas cuando apliqué la aceleración de hardware en partes de la página que ya estaban aceleradas. Por lo tanto, saber si tienes una superposición de aceleración es muy importante.

Para que la interacción del usuario sea lo más fluida posible y se parezca lo más posible al formato nativo, debemos hacer que el navegador funcione para nosotros. Idealmente, queremos que la CPU del dispositivo móvil configure la animación inicial y que la GPU se encargue de solo componer las diferentes capas durante el proceso de animación. Esto es lo que hacen Translate3d, scale3d y translateZ: dan a los elementos animados su propia capa, lo que permite que el dispositivo represente todo sin problemas. Para obtener más información sobre la composición acelerada y el funcionamiento de WebKit, Ariya Hidayat tiene mucha información útil en su blog.

Transiciones de página

Veamos tres de los enfoques más comunes de interacción del usuario al desarrollar una aplicación web móvil: efectos de deslizamiento, giro y rotación.

Puedes ver este código en acción aquí http://slidfast.appspot.com/slide-flip-rotate.html (Nota: Esta demostración está diseñada para dispositivos móviles; por lo tanto, activa un emulador, usa tu teléfono o tablet, o reduce el tamaño de la ventana del navegador a ~1024 px o menos).

Primero, analizaremos las transiciones de deslizamiento, giro y rotación, y veremos cómo se aceleran. Observa cómo cada animación solo toma tres o cuatro líneas de CSS y JavaScript.

Variable

El más común de los tres enfoques de transición; las transiciones de página deslizante imitan el aspecto nativo de las aplicaciones para dispositivos móviles. Se invoca la transición de deslizamiento para traer una nueva área de contenido al 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 que tenemos este concepto de páginas de etapa de pruebas a la izquierda o a la derecha. Básicamente, puede ser en cualquier dirección, pero 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 "blanco".

Cuando el usuario hace clic en un elemento de navegación, ejecutamos el siguiente JavaScript para intercambiar las clases. No se usan frameworks de terceros, es 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 convierten en stage-center y obligan a la página a deslizarse al puerto de vista central. 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 CSS que controla la detección y orientación de dispositivos móviles. Podríamos abordar todos los dispositivos y todas las resoluciones (consulta la resolución de consultas de contenido multimedia). En esta demostración, usé solo algunos ejemplos simples 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 de escritorio 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 Froyo 2.2+ de Android. 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 dispositivos móviles, girar se conoce como deslizar la página para que desaparezca. Aquí utilizamos JavaScript simple para manejar este evento en dispositivos iOS y Android (basados en WebKit).

Consúltalo en acción http://slidfast.appspot.com/slide-flip-rotate.html.

Cuando trabajas con eventos táctiles y transiciones, lo primero que debes hacer es saber 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;
}

Como estamos usando una transición de salida lenta CSS3 para el cambio de página, el elemento element.offsetLeft habitual no funcionará.

A continuación, queremos averiguar en qué dirección gira el usuario y establecer un umbral para que ocurra 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 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 CSS3 después de la activación de 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';
}

Traté de jugar con Bézier cúbica para dar la mejor sensación nativa a las transiciones, pero la salida lenta me ayudó.

Por último, para que la navegación ocurra, debemos llamar a los métodos slideTo() definidos con anterioridad 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 la página actual 180 grados para revelar el reverso. Para ello, toca la opción de menú “Contacto”. Una vez más, esto solo requiere unas pocas líneas de CSS y 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 funciones de transformación CSS 3D. Lamentablemente, en lugar de ignorar el giro, Android elimina la página girando en lugar de girarla. Recomendamos usar esta transición con moderación hasta que mejore la compatibilidad.

El lenguaje de marcado (concepto básico del frente y el 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 explicamos nuestras transiciones básicas, veamos la mecánica de cómo funcionan y se componen.

Para que esta sesión de depuración sea mágica, activa algunos navegadores y el IDE que prefieras. Primero, inicia Safari desde la línea de comandos para usar algunas variables de entorno de depuración. Utilizo una Mac, así que los comandos pueden variar según el SO que tengas. Abre la terminal y escribe lo siguiente:

  • $> exportar CA_COLOR_OPAQUE=1
  • $> exportar CA_LOG_MEMORY_USAGE=1
  • $> /Applications/Safari.app/Contents/MacOS/Safari.

Esto inicia Safari con algunos asistentes de depuración. CA_COLOR_OPAQUE nos muestra qué elementos están realmente compuestos o acelerados. CA_LOG_MEMORY_USAGE nos muestra cuánta memoria usamos cuando enviamos nuestras operaciones de dibujo al almacén de copia de seguridad. Esto te indica exactamente el esfuerzo que se está poniendo en el dispositivo móvil y, posiblemente, brinda pistas sobre cómo el uso de la GPU puede estar consumiendo la batería del dispositivo de destino.

A continuación, iniciemos Chrome para que podamos ver información correcta sobre los fotogramas por segundo (FPS):

  1. Abre el navegador web Google Chrome.
  2. En la barra de URL, escribe about:flags.
  3. Desplázate hacia abajo algunos elementos y haz clic en “Habilitar” para el contador de FPS.

Si ves esta página en tu versión actualizada de Chrome, verás el contador de FPS de color rojo en la esquina superior izquierda.

FPS de Chrome

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 hay alguna fuga (animaciones de ejecución continua que deberían detenerse).

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). Todos los elementos acelerados del DOM tienen un tono rojo. Esto nos muestra exactamente lo que compone la capa. Ten en cuenta que la barra de navegación blanca no está en rojo porque no está acelerada.

Contacto compuesto

También hay un parámetro de configuración similar disponible para Chrome en el artículo about:flags de "Bordes de la capa de renderización compuesta".

Otra buena manera de ver las capas compuestas es ver la demostración de hojas caídas de WebKit mientras se aplica esta modificación.

hojas ofrecidas

Por último, para comprender realmente el rendimiento del hardware de gráficos de nuestra aplicación, analicemos 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 Mac OS. Los búferes de memoria de Core Animation se comparten entre OpenGL ES y la GPU para crear los píxeles finales que se ven en la pantalla.

Coreanimation 1

Cuando simplemente cambiamos el tamaño o maximizamos la ventana del navegador, vemos que la memoria también se expande.

Coreanimation 2

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 depuras o pruebas tu entorno de iPhone, cambia el tamaño a 480 px por 320 px. Ahora sabemos exactamente cómo funciona la aceleración de hardware y qué se necesita para depurar. Es importante leer sobre ello, pero ver que los búferes de memoria de la GPU funcionan a nivel visual realmente pone las cosas en perspectiva.

Detrás de escena: recuperación y almacenamiento en caché

Ahora es el momento de llevar nuestra página y el almacenamiento en caché de recursos al siguiente nivel. Al igual que el enfoque que usan JQuery Mobile y frameworks similares, vamos a precargar y almacenar en caché nuestras páginas con llamadas simultáneas de AJAX.

Abordemos algunos problemas principales de la Web móvil y las razones por las que debemos hacerlo:

  • Recuperación: Precargar nuestras páginas permite a los usuarios descargar la aplicación sin conexión y también no permite tener espera entre acciones de navegación. Por supuesto, no queremos ahogar el ancho de banda del dispositivo cuando este se conecta, por lo que debemos usar esta función con moderación.
  • Almacenamiento en caché: A continuación, necesitamos un enfoque simultáneo o asíncrono para recuperar y almacenar en caché estas páginas. También debemos usar localStorage (ya que es compatible con los diferentes dispositivos), lo cual, lamentablemente, no es asíncrono.
  • AJAX y análisis de la respuesta: usar internalHTML() para insertar la respuesta AJAX en el DOM es peligroso (y no confiable). En su lugar, utilizamos 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 la xhr.responseText.

A partir del código de la demostración de deslizamiento, giro y rotación, primero agregaremos algunas páginas secundarias y los vincularemos a ellas. Luego, analizaremos los vínculos y crearemos transiciones sobre la marcha.

Pantalla principal de iPhone

Mira la demostración de la recuperación y la 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 nodo o clase que su página superior. Podríamos dar un paso más y usar el atributo data-* para nodos "page", etc. Y esta es la página de detalles (secundaria) ubicada en un archivo HTML separado (/demo2/home-detail.html), que se cargará, 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 JavaScript. Para simplificar, dejaré los asistentes u optimizaciones fuera del código. Lo único que hacemos aquí es recorrer en bucle un array específico de nodos del DOM para descubrir vínculos para recuperar y almacenar en caché. Nota: En esta demostración, se llama a este método fetchAndCache() cuando se carga la página. Volveremos a trabajar en ella 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') &amp;&amp;
      //'#' 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) &amp;&amp;
        //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();
      }
    }
  }
};

Para garantizar un posprocesamiento asíncrono adecuado, usamos el objeto “AJAX”. Hay una explicación más avanzada del uso de localStorage dentro de una llamada de AJAX en Cómo trabajar sin conexión en la cuadrícula con HTML5. En este ejemplo, verás el uso básico del almacenamiento en caché en cada solicitud y de la provisión de los objetos almacenados en caché cuando el servidor no muestre una respuesta 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, dado que localStorage usa UTF-16 para la codificación de caracteres, cada byte se almacena como 2 bytes, lo que hace que nuestro límite de almacenamiento pase de 5 MB a 2.6 MB en total. En la siguiente sección, se revela toda la razón por la cual se deben recuperar y almacenar 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 manera simple y eficaz de analizar el elemento responseText que recibimos de nuestra llamada AJAX. Existen muchos analizadores de JavaScript y expresiones regulares de 3000 líneas que quitan etiquetas de secuencias de comandos, entre otras opciones. 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. Estamos utilizando el atributo HTML5 “sandbox” que inhabilita las secuencias de comandos y ofrece muchas funciones de seguridad...

Desde la especificación: Cuando se especifica, el atributo de zona de pruebas habilita un conjunto de restricciones adicionales para cualquier contenido alojado por el iframe. Su valor debe ser un conjunto sin ordenar 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 configura el atributo, se considera que el contenido proviene 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 se niega de manera correcta a mover un nodo de manera implícita 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.

¿Por qué implementar iframe? ¿Por qué no solo usar internalHTML? Si bien internalHTML ahora forma parte de la especificación de HTML5, es una práctica peligrosa insertar la respuesta de un servidor (mal o bueno) en un área no marcada. Cuando se redactó este artículo, no pude encontrar a nadie que utilice otro solo HTML interno. Sé que JQuery lo usa en esencia con un resguardo de anexos solo en excepciones. Y JQuery Mobile también lo usa. Sin embargo, no realicé ninguna prueba rigurosa sobre el “deja de funcionar al azar” del HTML interno, pero sería muy interesante ver todas las plataformas que esto afecta. También sería interesante ver qué enfoque tiene el mejor rendimiento... También escuché afirmaciones de ambas partes sobre este tema.

Detección, control y generación de perfiles del tipo de red

Ahora que tenemos la capacidad de almacenar en búfer (o almacenar en caché predictivo) nuestra aplicación web, debemos proporcionar las funciones de detección de conexión adecuadas que hacen que nuestra aplicación sea más inteligente. Aquí es donde el desarrollo de aplicaciones para dispositivos móviles se vuelve extremadamente sensible a los modos en línea/sin conexión y a la velocidad de conexión. Ingresa la API de Network Information. Cada vez que muestro esta función en una presentación, un miembro del público levanta la mano y pregunta: "¿Para qué usaría esa función?". Así que aquí hay una forma posible de configurar una aplicación web móvil extremadamente inteligente.

Aburrida situación de sentido común primero... Mientras interactúas con la Web desde un dispositivo móvil en un tren de alta velocidad, la red puede desaparecer muy bien en varios momentos, y las diferentes ubicaciones geográficas podrían admitir velocidades de transmisión diferentes (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.

En el siguiente código, se proporciona lo siguiente:

  • Acceso sin conexión a través de applicationCache.
  • Detecta si está agregado a favoritos y sin conexión.
  • Detecta cuando se pasa del modo 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 indicar a nuestro código si se lo llama desde un evento o desde una solicitud o actualización de la página real. Esto se debe a que el evento onload del cuerpo no se activará cuando se cambie entre el modo en línea y el modo sin conexión.

A continuación, tenemos una verificación simple de un evento ononline o onload. Este código restablece los vínculos inhabilitados cuando se pasa de sin conexión a en línea, pero si la app fuera más sofisticada, podrías insertar una lógica que reanude la recuperación de contenido o controle la UX en el caso de 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 lleven a cabo 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 SIEMPRE, ¡mujajaja!

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);

    }
  }
}

Muy bien, pasemos 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. Incluí los comentarios sobre las latencias y las descargas de los proveedores estadounidenses típicos de América del Norte para cada conexión.

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);
  }
}

Hay numerosos ajustes que podemos realizar en nuestro proceso fetchAndCache, pero lo único que hice aquí fue indicarle que recupere los recursos asíncronos (verdadero) o síncronos (falso) para una conexión determinada.

Cronograma de solicitud perimetral (síncrona)

Sincronización perimetral

Cronograma de solicitudes WIFI (asíncronas)

WI-FI asíncrona

Esto permite aplicar, al menos, algún método de ajuste de la experiencia del usuario basado en conexiones lentas o rápidas. No se trata de una solución definitiva. Otra tarea pendiente sería mostrar una ventana modal de carga cuando se hace clic en un vínculo (en conexiones lentas) mientras la app aún puede obtener la página de ese vínculo en segundo plano. Lo importante aquí es reducir las latencias y, al mismo tiempo, aprovechar todas las capacidades de la conexión del usuario con el mejor y más reciente HTML5 que puede ofrecer. Mira la demostración de detección de red aquí.

Conclusión

El recorrido de las aplicaciones HTML5 para celulares recién está empezando. Ahora puedes ver los fundamentos muy simples y básicos de un "marco de trabajo" móvil construido exclusivamente en torno a HTML5 y que es compatible con tecnologías. Creo que es importante que los desarrolladores trabajen con estas funciones y las aborde en su núcleo y que no estén enmascaradas por un wrapper.