Servir medios adaptativos usando Service Workers
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
| ECT | RTT Mínimo (ms) | Downlink Máximo (Kbps) | Explicación |
|---|---|---|---|
| slow‑2g | 2000 | 50 | La red es adecuada solo para transferencias pequeñas como páginas de solo texto. |
| 2g | 1400 | 70 | La red es adecuada para transferencias de imágenes pequeñas. |
| 3g | 270 | 700 | La red es adecuada para transferencias de activos grandes como imágenes de alta resolución, audio y video SD. |
| 4g | 0 | ∞ | La red es adecuada para video HD, video en tiempo real, etc. |
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.

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.