VOLVER

Servir medios adaptativos usando Service Workers

6 min de lectura

Haciendo pair con @Ester Martí

Todos hemos experimentado cómo visitar un sitio web a través de una conexión de red lenta suele tardar una eternidad en cargarse, haciendo que la experiencia sea muy dolorosa o completamente imposible.

Cuando se trata de desarrollo web, solemos olvidar el rendimiento de carga centrándonos más en añadir nuevas características llamativas. Pero probablemente nuestros usuarios no están usando el último MacBook Pro conectado a una red de 1Gps. Es más probable que estén usando un dispositivo móvil de gama media o baja con una conexión de red que en el mejor de los casos es una conexión 3G.

En 2018, el 52.2% de todas las páginas web globales se sirvieron a teléfonos móviles.

Por lo tanto, cuidar el rendimiento es importante y uno de los que más recursos consume es la entrega de medios. Vamos a mostrar cómo adaptar la entrega de medios basada en la conexión de red utilizando la API de Información de Red. Esta es una versión mejorada de un experimento que hice con mi compañero @Eduardo Aquiles como un componente de React, similar a lo que Max Böck explica en su artículo sobre componentes conscientes de la conexión pero en este caso, utilizando service workers.

La API de Información de Red

La API de Información de Red es un borrador de especificación que expone una interfaz a JavaScript con información sobre la conexión del dispositivo.

La interfaz consta de un conjunto diferente de atributos que nos da múltiple información sobre la red. Los más relevantes para nosotros en este artículo son:

  • type: El tipo de conexión que el agente de usuario está utilizando. (p.ej. ‘wifi’, ‘cellular’, ‘ethernet’, etc.)
  • effectiveType El tipo de conexión efectiva que se determina utilizando una combinación de rtt y downlink observados recientemente. (ver tabla)
  • saveData Indica cuando el usuario solicitó un uso reducido de datos.

valores de effectiveType

ECTRTT Mínimo (ms)Downlink Máximo (Kbps)Explicación
slow‑2g200050La red es adecuada solo para transferencias pequeñas como páginas de solo texto.
2g140070La red es adecuada para transferencias de imágenes pequeñas.
3g270700La red es adecuada para transferencias de activos grandes como imágenes de alta resolución, audio y video SD.
4g0La red es adecuada para video HD, video en tiempo real, etc.
Tabla de tipos de conexión efectiva (ECT)

Soporte del navegador

La API aún no tiene un soporte completo del navegador pero es soportada por los navegadores móviles más populares que son donde esta técnica tendrá más impacto.

Soporte del navegador para la API de Información de Red

De hecho, el 70% de los usuarios móviles tienen esta API habilitada en su dispositivo.

Servir Medios Adaptativos

Nuestro propósito será servir diferentes recursos multimedia basados en la información que obtenemos del atributo effectiveType. Cuando hablamos de diferentes recursos multimedia podría ser un medio completamente diferente, como cambiar entre video HD, imagen HD o imagen de baja calidad, el enfoque sugerido por Addy Osmani.

En este ejemplo, vamos a utilizar diferentes niveles de compresión para la misma imagen.

Primero, necesitamos obtener la calidad adecuada basada en las condiciones de la red. Esto es fácilmente alcanzable utilizando el siguiente fragmento:

function getMediaQuality() {
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (!connection) {
return 'medium';
}
switch (connection.effectiveType) {
case 'slow-2g':
case '2g':
return 'low';
case '3g':
return 'medium';
case '4g':
return 'high';
default:
return 'low';
}
}

Imagina que tenemos un servidor de imágenes donde podemos especificar la calidad de la imagen que queremos con un parámetro de consulta quality como low, medium o high. Por lo tanto podemos establecer la calidad en el atributo src de las etiquetas de imágenes como sigue:

<img src="http://images.magarcia.io/cute_cat?quality=low" alt="Gato lindo" />
const images = document.querySelectorAll('img');
images.forEach(img => {
img.src = img.src.replace('low', getMediaQuality());
});

Es importante notar que la calidad por defecto establecida en la imagen es low, lo que significa que los dispositivos cargarán primero la imagen de baja calidad y luego si tiene una conexión de alta velocidad cargará la de mejor calidad.

Luego el fragmento de JavaScript anterior obtendrá todas las imágenes en el documento y reemplazará el parámetro de calidad al apropiado basado en lo que la función getMediaQuality devuelve. Si la calidad es low no va a hacer más peticiones, pero si cambia hará dos peticiones: una con la imagen de calidad low cuando los navegadores analizan la etiqueta img y otra con calidad medium o high cuando se ejecuta el código JavaScript.

Esto no es ideal pero mejorará los tiempos de carga en redes lentas. Pero para redes de conexión media/alta, como mencionamos antes, hará dos peticiones para cada imagen consumiendo más datos de los necesarios.

Usando Service Workers

El problema mencionado con respecto a las dos peticiones puede ser arreglado usando service workers, interceptando la petición hecha por el navegador y reemplazándola con la calidad apropiada para la imagen.

Primero, necesitamos registrar nuestro service worker:

if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(
function(registration) {
console.log('Registro de ServiceWorker exitoso con alcance: ', registration.scope);
},
function(err) {
console.log('Registro de ServiceWorker fallido: ', err);
}
);
});
}

A continuación, añadimos un listener para el evento fetch, que para todas las imágenes solicitadas desde el sitio añadirá el parámetro de calidad correcto usando la función getMediaQuality creada en la sección anterior.

self.addEventListener('fetch', function(event) {
if (/\.jpg$|.png$|.webp$/.test(event.request.url)) {
const url = event.request.url + `?quality=${getMediaQuality()}`;
event.respondWith(fetch(url));
}
});

Y ya no necesitamos especificar el parámetro de calidad en la etiqueta img ya que el service worker se encargará de eso.

<img src=“http://images.magarcia.io/cute_cat” alt=“Gato lindo”/>

El código

Puedes encontrar el código de este post (más completo, limpio y con menos errores) en este repositorio de GitHub.

Lectura Adicional