Crear contenido para la Web te brinda un alcance sin precedentes. Tu aplicación web está a un clic de distancia y está disponible en casi todos los dispositivos conectados: smartphones, tablets, laptops, computadoras de escritorio, TVs y mucho más, independientemente de la marca o la plataforma. Para brindar la mejor experiencia, compilaste un sitio responsivo que adapta la presentación y la funcionalidad para cada factor de forma. Ahora, ejecutas tu lista de tareas de rendimiento para asegurarte de que la aplicación se cargue lo más rápido posible: optimizaste tu ruta de renderización crítica, comprimes y almacenaste en caché tus recursos de texto, y ahora consideras la mayoría de los recursos de imagen, que suelen transferirse. El problema es que la optimización de imágenes es difícil:
- Determinar el formato adecuado (vector frente a trama)
- Determinar los formatos de codificación óptimos (jpeg, webp, etcétera)
- Determina la configuración de compresión adecuada (con pérdida o sin pérdida)
- Determina qué metadatos se deben conservar o quitar
- Crea múltiples variantes de cada una para cada pantalla y resolución de DPR.
- ...
- Considera el tipo de red, la velocidad y las preferencias del usuario
De forma individual, estos son problemas bien comprendidos. En conjunto, crean un gran espacio de optimización que nosotros (los desarrolladores) a menudo pasamos por alto o descuidamos. Los seres humanos hacen un mal trabajo al explorar el mismo espacio de búsqueda de forma repetitiva, en especial cuando involucran muchos pasos. Las computadoras, por otro lado, se destacan en este tipo de tareas.
La respuesta a una estrategia de optimización buena y sustentable para imágenes y otros recursos con propiedades similares es simple: la automatización. Si ajustas manualmente tus recursos, lo harás de forma incorrecta: te olvidarás, te volverás perezoso o alguien más cometerá estos errores por ti, garantizado.
La saga del desarrollador enfocado en el rendimiento
La búsqueda en el espacio de optimización de imágenes tiene dos fases distintas: el tiempo de compilación y el tiempo de ejecución.
- Algunas optimizaciones son intrínsecas al recurso en sí; p.ej., la selección del formato y el tipo de codificación adecuados, el ajuste de la configuración de compresión de cada codificador, la eliminación de metadatos innecesarios, etcétera. Estos pasos se pueden realizar en el “momento de la compilación”.
- Otras optimizaciones se determinan según el tipo y las propiedades del cliente que las solicita, y deben realizarse en el "tiempo de ejecución": se selecciona el recurso adecuado para la DPR del cliente y el ancho de pantalla previsto, teniendo en cuenta la velocidad de red del cliente, las preferencias del usuario y de la aplicación, etc.
Las herramientas de tiempo de compilación existen, pero podrían mejorarse. Por ejemplo, puedes ahorrar mucho dinero si ajustas dinámicamente el parámetro de configuración de “calidad” de cada imagen y cada formato de imagen, pero no veo a nadie lo use fuera de la investigación. Esta es un área maduro para la innovación, pero, a los efectos de esta publicación, lo dejaré así. Enfoquémonos en la parte del tiempo de ejecución.
<img src="/image/thing" sizes="50vw"
alt="image thing displayed at 50% of viewport width">
El intent de la aplicación es muy simple: recupera y muestra la imagen en el 50% del viewport del usuario. Aquí es donde la mayoría de los diseñadores se lavan las manos y la cabeza para ir al bar. Mientras tanto, el desarrollador del equipo que se preocupa por el rendimiento trabaja durante una larga noche:
- Para obtener la mejor compresión, quiere usar el formato de imagen óptimo para cada cliente: WebP para Chrome, JPEG XR para Edge y JPEG para el resto.
- Para obtener la mejor calidad visual, necesita generar múltiples variantes de cada imagen con diferentes resoluciones: 1x, 1.5x, 2x, 2.5x, 3x y tal vez incluso unas pocas más intermedias.
- Para evitar proporcionar píxeles innecesarios, debe comprender qué significa "en realidad el 50% del viewport del usuario". Existen muchos anchos de viewport diferentes.
- Idealmente, también desea ofrecer una experiencia resiliente en la que los usuarios de redes más lentas recuperarán automáticamente una resolución más baja. Después de todo, es cuestión de vidrio.
- La aplicación también expone algunos controles de usuario que afectan qué recurso de imagen se debe recuperar, por lo que también debes tenerlo en cuenta.
Entonces, la diseñadora se da cuenta de que debe mostrar una imagen diferente al 100% del ancho si el tamaño del viewport es pequeño para optimizar la legibilidad. Esto significa que ahora tenemos que repetir el mismo proceso para otro recurso y, luego, hacer que la recuperación sea condicional en el tamaño del viewport. ¿Mencioné que esto es difícil? Bueno,
vamos a empezar. El elemento picture
nos llevará bastante lejos:
<picture>
<!-- serve WebP to Chrome and Opera -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
/image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
/image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
type="image/webp">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
/image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
/image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
type="image/webp">
<!-- serve JPEGXR to Edge -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
/image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
/image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
type="image/vnd.ms-photo">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
/image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
/image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
type="image/vnd.ms-photo">
<!-- serve JPEG to others -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
/image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
/image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
/image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
/image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
<!-- fallback for browsers that don't support picture -->
<img src="/image/thing.jpg" width="50%">
</picture>
Manejamos la dirección artística y la selección de formato, y proporcionamos seis variantes de cada imagen para tener en cuenta la variabilidad de la DPR y el ancho de la vista del puerto del dispositivo del cliente. ¡Impresionante!
Lamentablemente, el elemento picture
no nos permite definir ninguna regla sobre cómo debe comportarse en función del tipo de conexión o la velocidad del cliente. Dicho esto, su algoritmo de procesamiento permite que el usuario-agente ajuste qué recurso recupera en algunos casos; consulta el paso 5. Solo esperamos que el usuario-agente sea lo suficientemente
inteligente. (Nota: Ninguna de las implementaciones actuales lo es). Del mismo modo, no hay hooks en el elemento picture
que permita la lógica específica de la app que tenga en cuenta las preferencias de la app o del usuario. Para obtener estos dos últimos bits, tendríamos que mover toda la lógica anterior a JavaScript, pero eso perderá las optimizaciones del análisis de precarga que ofrece picture
. Mmmm.
Dejando de lado esas limitaciones, funciona. Bueno, al menos para este recurso en particular. El desafío real y a largo plazo es que no podemos esperar que el diseñador o el desarrollador creen código como este para todos y cada uno de los recursos. Es un rompecabezas divertido en el primer intento, pero pierde su atractivo inmediatamente después de eso. Necesitamos la automatización. Tal vez el IDE o alguna otra herramienta de transformación de contenido nos puedan ahorrar y generar automáticamente el código estándar mencionado anteriormente.
Automatiza la selección de recursos con sugerencias de clientes
Respira profundo, suspende tu incredulidad y considera el siguiente ejemplo:
<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
<source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
<img sizes="100vw" src="/image/thing-crop">
</picture>
Lo creas o no, el ejemplo anterior es suficiente para brindar las mismas capacidades que el lenguaje de marcado de imágenes más largo anterior y, como veremos, permite controlar por completo el desarrollador sobre cómo, cuáles y cuándo se recuperan los recursos de imagen. El "mágico" se encuentra en la primera línea que permite los informes de sugerencias de clientes y le indica al navegador que anuncie la proporción de píxeles del dispositivo (DPR
), el ancho del viewport del diseño (Viewport-Width
) y el ancho de pantalla previsto (Width
) de los recursos al servidor.
Con las sugerencias de clientes habilitadas, el lenguaje de marcado del cliente resultante retiene solo los requisitos de presentación. El diseñador no tiene que preocuparse por los tipos de imagen, las resoluciones de cliente, los puntos de interrupción óptimos para reducir los bytes entregados ni otros criterios de selección de recursos. Seamos realistas, nunca lo hicieron y no deberían tener que hacerlo. Mejor aún, el desarrollador tampoco necesita reescribir y expandir el lenguaje de marcado anterior porque el cliente y el servidor negocia la selección real del recurso.
Chrome 46 proporciona compatibilidad nativa para las sugerencias de DPR
, Width
y Viewport-Width
. Las sugerencias están inhabilitadas de forma predeterminada, y la <meta http-equiv="Accept-CH" content="...">
anterior sirve como un indicador de aceptación que le indica a Chrome que agregue los encabezados especificados a las solicitudes salientes. Una vez hecho esto, examinemos los encabezados de la solicitud y la respuesta para una solicitud de imagen de muestra:
Chrome anuncia su compatibilidad con el formato WebP a través del encabezado de solicitud Accept; el nuevo navegador Edge anuncia la compatibilidad con JPEG XR a través del encabezado Accept.
Los siguientes tres encabezados de solicitud son los encabezados sugeridos por el cliente que anuncian la proporción de píxeles del dispositivo del cliente (3x), el ancho del viewport del diseño (460 px) y el ancho de pantalla previsto del recurso (230 px). Esto proporciona toda la información necesaria al servidor para seleccionar la variante de imagen óptima según su propio conjunto de políticas: disponibilidad de recursos pregenerados, costo de volver a codificar o cambiar el tamaño de un recurso, popularidad de un recurso, carga actual del servidor, etcétera. En este caso particular, el servidor usa las sugerencias DPR
y Width
, y muestra un recurso WebP, como lo indican los encabezados Content-Type
, Content-DPR
y Vary
.
Aquí no hay magia. Cambiamos la selección de recursos del lenguaje de marcado HTML a la negociación de solicitud-respuesta entre el cliente y el servidor. Como resultado, el HTML solo se relaciona con los requisitos de presentación y es algo que podemos confiar en que cualquier diseñador y desarrollador puede escribir, mientras que la búsqueda en el espacio de optimización de imágenes se aplaza a computadoras y ahora se automatiza a gran escala con facilidad. ¿Recuerdas a nuestro desarrollador que se preocupa por el rendimiento? Su trabajo ahora es escribir un servicio de imágenes que pueda aprovechar las sugerencias proporcionadas y mostrar la respuesta adecuada: puede usar cualquier lenguaje o servidor que desee, o dejar que un servicio de terceros o una CDN lo haga por ella.
<img src="/image/thing" sizes="50vw"
alt="image thing displayed at 50% of viewport width">
Además, ¿recuerdas al tipo de arriba? Con las sugerencias de cliente, la etiqueta de imagen sencilla ahora reconoce la DPR, la viewport y el ancho sin ningún lenguaje de marcado adicional. Si necesitas agregar una dirección de arte, puedes usar la etiqueta picture
, como se muestra más arriba. De lo contrario, todas tus etiquetas de imagen existentes serán mucho más inteligentes. Las sugerencias de cliente mejoran los elementos img
y picture
existentes.
Cómo tomar el control de la selección de recursos con un service worker
ServiceWorker es, de hecho, un proxy del lado del cliente que se ejecuta en tu navegador. Intercepta todas las solicitudes salientes y te permite inspeccionar, reescribir, almacenar en caché y hasta sintetizar respuestas. Las imágenes no son diferentes y, con las sugerencias de cliente habilitadas, el ServiceWorker activo puede identificar las solicitudes de imagen, inspeccionar las sugerencias de cliente proporcionadas y definir su propia lógica de procesamiento.
self.onfetch = function(event) {
var req = event.request.clone();
console.log("SW received request for: " + req.url)
for (var entry of req.headers.entries()) {
console.log("\t" + entry[0] +": " + entry[1])
}
...
}
ServiceWorker te brinda control total del lado del cliente sobre la selección de recursos. Esto es fundamental. Déjalo pasar, ya que las posibilidades son casi infinitas:
- Puedes reescribir los valores de encabezado de las sugerencias de cliente que configura el usuario-agente.
- Puedes agregar nuevos valores de encabezados de sugerencias de cliente a la solicitud.
- Puedes reescribir la URL y apuntar la solicitud de imagen a un servidor alternativo
(p.ej., CDN).
- Incluso puedes mover los valores de sugerencia desde los encabezados hasta la URL si eso facilita la implementación en tu infraestructura.
- Puedes almacenar respuestas en caché y definir tu propia lógica para la que se entregan los recursos.
- Puedes adaptar tu respuesta en función de la conectividad de los usuarios.
- Puedes usar la API de NetInfo para consultar tus preferencias y anunciarlas al servidor.
- Puedes mostrar una respuesta alternativa si la recuperación es lenta.
- Puedes considerar las anulaciones de preferencias de aplicaciones y usuarios.
- Puedes... hacer lo que tu corazón desee, realmente.
El elemento picture
proporciona el control de dirección de arte necesario en el lenguaje de marcado HTML.
Las sugerencias de cliente proporcionan anotaciones en las solicitudes de imágenes resultantes que habilitan la automatización de la selección de recursos. ServiceWorker brinda funciones de administración
de solicitudes y respuestas al cliente. Esta es la Web extensible en acción.
Preguntas frecuentes sobre las sugerencias de cliente
¿Dónde están disponibles las sugerencias de clientes? Se envía en Chrome 46. Se están evaluando en Firefox y Edge.
¿Por qué se habilitan las sugerencias de clientes? Queremos minimizar la sobrecarga de los sitios que no usan sugerencias de clientes. Para habilitar las sugerencias de clientes, el sitio debe proporcionar el encabezado
Accept-CH
o la directiva<meta http-equiv>
equivalente en el lenguaje de marcado de la página. Con cualquiera de ellos presente, el usuario-agente adjuntará las sugerencias correspondientes a todas las solicitudes de los subrecursos. En el futuro, es posible que proporcionemos un mecanismo adicional para conservar esta preferencia para un origen en particular, lo que permitirá que se entreguen las mismas sugerencias en las solicitudes de navegación.¿Por qué necesitamos sugerencias de clientes si tenemos ServiceWorker? ServiceWorker no tiene acceso a la información del diseño, los recursos y el ancho del viewport. Al menos, esto no ocurrirá sin introducir recorridos costosos y retrasar significativamente la solicitud de imagen (p.ej., cuando el analizador de precarga inicia una solicitud de imagen). Las sugerencias de clientes se integran con el navegador para que estos datos estén disponibles como parte de la solicitud.
¿Las sugerencias de clientes son solo para recursos de imagen? El caso de uso principal detrás de las sugerencias de DPR, ancho de ventana de visualización y ancho es habilitar la selección de recursos para los recursos de imagen. Sin embargo, se entregan las mismas sugerencias para todos los subrecursos, independientemente del tipo; p.ej., las solicitudes de CSS y JavaScript también obtienen la misma información y se pueden usar para optimizar esos recursos.
Algunas solicitudes de imagen no informan el ancho, ¿por qué? Es posible que el navegador no conozca el ancho de visualización previsto porque el sitio depende del tamaño intrínseco de la imagen. Como resultado, se omite la sugerencia para el ancho en dichas solicitudes y en las que no tienen "ancho de visualización", p.ej., un recurso de JavaScript. Para recibir sugerencias de ancho, asegúrate de especificar un valor de tamaño en tus imágenes.
¿Qué sucede con <insert myfavorite hint>? ServiceWorker permite a los desarrolladores interceptar y modificar (p.ej., agregar encabezados nuevos) todas las solicitudes salientes. Como ejemplo, es fácil agregar información basada en NetInfo para indicar el tipo de conexión actual; consulta "Informes de capacidades con ServiceWorker". Las sugerencias "nativas" que se envían en Chrome (DPR, ancho, ancho de recurso) se implementan en el navegador, ya que una implementación pura basada en SW retrasaría todas las solicitudes de imágenes.
¿Dónde puedo obtener más información, ver más demostraciones? Consulta el documento explicativo y no dudes en abrir un problema en GitHub si tienes comentarios o preguntas.