Cómo la directiva NgOptimizedImage calibra las imágenes para ser representadas eficientemente
En el ámbito del desarrollo web, optimizar las imágenes para una adecuada representación visual es crucial. Este artículo resalta los ajustes que efectúa la directiva NgOptimizedImage, esa característica del framework de Angular que permite de manera sencilla comunicar a los navegadores cuáles imágenes priorizar y cuáles posponer, además de ayudar con la configuración de imágenes adaptativas (en inglés, responsive) y configurar automáticamente marcadores de posición de imágenes, mejorando la velocidad de carga percibida del contenido web para nuestros usuarios.
Activemos la directiva NgOptimizedImage importándola desde el paquete @angular/commony reemplazando el atributo src del elemento de imagen, en el ejemplo de código, con el atributo ngSrc. Esta sustitución transfiere el control del elemento a la directiva, que genera durante la compilación el <img> resultante con enlaces a imágenes cargadas selectivamente por el navegador de acuerdo con las capacidades del dispositivo y las condiciones de la red del usuario.
https://medium.com/media/89ad8284096494d08b1970e011144944/href
En el ejemplo, obtenemos la imagen desde Unsplash, la biblioteca de fotos adoptada oficialmente por la plataforma de publicación Medium, utilizando un cargador de imágenes personalizado.
Optar por un cargador de imágenes personalizado o uno incorporado — de los que ofrecen soporte para servicios de imágenes de terceros como Cloudflare, Imgix y Netlify — en vez de usar un cargador genérico sin transformaciones de URL, permite una mayor optimización a través de características específicas del CDN, como el redimensionamiento, selección del formato y la calidad de la imagen.
Acá está el HTML resultante de la plantilla del componente Angular después de la compilación en modo de producción:
<app-optimized-unsplash-image>
<article>
<img
alt=”Optimized Unsplash image”
ngsrc=”0*hiSjMcbdqbV35wRI”
width=”600″
height=”400″
decoding=”sync”
priority=””
loading=”eager”
fetchpriority=”high”
ng-img=”true”
src=”https://cdn-images-1.medium.com/max/640/0*hiSjMcbdqbV35wRI”
srcset=”https://cdn-images-1.medium.com/max/600/0*hiSjMcbdqbV35wRI 1x,
https://cdn-images-1.medium.com/max/1200/0*hiSjMcbdqbV35wRI 2x”>
</article>
</app-optimized-unsplash-image>
Optimizando la carga de imágenes
La directiva NgOptimizedImage utiliza el atributo priority para marcar las imágenes a las que el navegador debe otorgar prioridad de procesamiento, mejorando su tiempo de carga ante indicadores de rendimiento web como la Web Vital LCP.
Esta métrica mide la rapidez con la que el navegador puede representa el elemento visible más grande que tiene contenido de texto, imagen o video. Este uno de los pasos culminantes de la Ruta de representación crítica (en inglés, Critical Rendering Path) que nos da la confianza de que nuestros usuarios recibirán contenido relevante en tiempo.
Esta secuencia de carga inicia cuando el navegador comienza a buscar y analizar el documento HTML para convertir el marcado y las reglas de estilo en estructuras lógicas, como los árboles DOM y CSSOM. Al mismo tiempo, el código JavaScript analizado está siendo compilado e interpretado, manipulando estos árboles antes de que se ejecuten los cálculos geométricos y de composición visual de la manera más eficiente posible para poder dibujar los elementos web resultantes en la pantalla.
El panel de Rendimiento de la herramienta DevTools puede ayudarnos a visualizar e inspeccionar cualquier cuello de botella para optimizar la secuencia de representación y alcanzar el evento LCP lo antes posible.
Ejecutando la herramienta Rendimiento de DevTools para inspeccionar los detalles del LCP y sus eventos precedentes.
En modo de desarrollo, la directiva NgOptimizedImage ejecuta un mecanismo de detección basado en un PerformanceObserver que vigila los eventos LCP. Supongamos que el elemento LCP resulta ser una imagen optimizada por Angular que no tiene el atributo priority. En este caso, la consola del navegador nos va a mostrar un error en tiempo de ejecución alertándonos a etiquetar la imagen adecuadamente para obtener mejores resultados de rendimiento web.
La consola del navegador reportando un error en tiempo de ejecución si una imagen LCP no se declara como prioritaria.
La directiva entonces puede ajustar automáticamente el comportamiento de carga de las imágenes basándose en la presencia del atributo priority. Las imágenes con esta instrucción reciben los atributos fetchpriority=”high” y loading=”eager”, indicando al navegador que las obtenga y represente visualmente tan pronto como su nodo HTML sea procesado.
Las imágenes restantes, consideradas no esenciales, reciben el atributo loading=”lazy”, permitiendo al navegador centrarse primero en representar el contenido más prioritario. Esta estrategia (introducida como característica nativa en los navegadores basados en Chromium en 2019 y totalmente disponible en todos los navegadores modernos desde 2022) pospone la carga de estas imágenes hasta que el navegador estime que sean necesarias — por ejemplo, cuando estén casi a la vista a medida que un usuario se desplaza o hace clic hacia ellas.
Noten que el valor del atributo decoding=”sync” fue adicionado manualmente a la imagen prioritaria como una pista adicional al navegador para que la decodifique junto con otras tareas de procesamiento. Y a la viceversa, para obtener una mejora adicional del rendimiento en imágenes cargadas “perezosamente”, podemos declarar el atributo decoding=”async” para permitir que otro contenido se represente visualmente antes de que los datos de imagen desde un formato comprimido sean decodificados previo a la etapa que finalmente dibuja los píxeles en pantalla.
También pueden notar que todas las imágenes procesadas por la directiva NgOptimizedImage están marcadas con el atributo ng-img, para que podamos identificarlas con precisión durante las pruebas sintéticas o el monitoreo de usuarios reales y medir su verdadero impacto en el rendimiento general de la aplicación.
Construyendo una imagen adaptativa
Otro atributo que la directiva NgOptimizedImage genera automáticamente es un srcset de imágenes cuando un cargador personalizado o de terceros es utilizado, listando imágenes candidatas para que los navegadores visualicen bajo condiciones adaptativas específicas, como la dimensión del puerto de visualización (en inglés, viewport) o la densidad de píxeles de la pantalla del dispositivo. Esta característica nativa del navegador no solo potencialmente reduce el tamaño de descarga de las imágenes, sino que también apoya la creación de diseños adaptativos, optimizando las experiencias de usuario a través de dispositivos diferentes.
Si el atributo sizes no está presente como en el ejemplo anterior, se calculará un srcset fijo utilizando los descriptores de densidad de píxeles predeterminados 1x y 2x, más la información obtenida del atributo width y el parámetro config.width utilizado en el cargador de imágenes.
Al representar el ejemplo de código sin el atributo `sizes`, se genera un atributo `srcset` con rutas de imagen que coinciden con los tamaños correspondientes a los descriptores de densidad de píxeles predeterminados.
Para una mejor dirección artística, es óptimo especificar el atributo sizes con valores que reflejen el ancho de imagen esperado para cada condición visual. La directiva NgOptimizedImage establece por defecto en dieciséis puntos de interrupción (en inglés, breakpoints), por lo que proporcionar nuestra propia lista basada en el diseño de nuestra aplicación puede reducir la longitud de los valores de srcset generados automáticamente a nuestros requerimientos específicos.
https://medium.com/media/9e02bd51b36669de616ed661737ef457/href
El resultado HTML compilado ahora muestra un srcset personalizado generado por la directiva utilizando la información del atributo sizes.
<app-optimized-unsplash-image>
<article>
<img
alt=”Optimized Unsplash Image”
ngsrc=”0*hiSjMcbdqbV35wRI”
width=”600″
height=”400″
decoding=”sync”
sizes=”(max-width: 400px) 100vw,
(max-width: 1200px) 50vw,
100vw”
priority=””
loading=”eager”
fetchpriority=”high”
ng-img=”true”
src=”https://cdn-images-1.medium.com/max/640/0*hiSjMcbdqbV35wRI”
srcset=”https://cdn-images-1.medium.com/max/380/0*hiSjMcbdqbV35wRI 380w,
https://cdn-images-1.medium.com/max/640/0*hiSjMcbdqbV35wRI 640w,
https://cdn-images-1.medium.com/max/1200/0*hiSjMcbdqbV35wRI 1200w,
https://cdn-images-1.medium.com/max/1920/0*hiSjMcbdqbV35wRI 1920w,
https://cdn-images-1.medium.com/max/2048/0*hiSjMcbdqbV35wRI 2048w,
https://cdn-images-1.medium.com/max/3840/0*hiSjMcbdqbV35wRI 3840w”>
</article>
</app-optimized-unsplash-image>
Si necesitásemos proporcionar imágenes candidatas utilizando descriptores de densidad de píxeles, la directiva NgOptimizedImage nos permite introducir en una entrada ngSrcset una lista de descriptores de ancho o densidad de píxeles, pero no una combinación de ambos. Intentar declarar valores combinados como ngSrcset=”400w, 600w, 1200w, 1x, 2x” genera el siguiente error en tiempo de ejecución.
La consola del navegador informa de un error en tiempo de ejecución por un valor inválido de `ngSrcset`.
Además, definir valores de descriptores de densidad de píxeles demasiado altos devuelve un error en modo de desarrollo. La directiva está diseñada para soportar hasta un máximo de 3x de densidad, con la recomendación de no exceder 2x basada en las capacidades del sistema visual humano. Valores más altos aumentarían innecesariamente del tamaño del archivo sin una mejora perceptible en la claridad de la imagen para el usuario, lo que conduce a tiempos de carga más lentos.
La consola del navegador mostrando un error en tiempo de ejecución debido a la mención de un descriptor de densidad de píxeles 4x, irrazonablemente alto, en el valor del atributo `ngSrcset`.
Si necesitamos controlar de una forma más detallada la selección de imágenes a partir de condiciones adaptativas complejas, es una opción válida optar porque el atributo srcset no se genere automáticamente al usar cargadores de imágenes. Esto se puede lograr fácilmente estableciendo el atributo disableOptimizedSrcset=”true” en la etiqueta de la imagen optimizada.
Generando sugerencias de carga especulativa
Como desarrolladores, tenemos la oportunidad de informar a los navegadores sobre qué imágenes son candidatas para solicitudes de procesamiento anticipado antes de que se cumplan sus condiciones de representación visual.
El constructor de paquetes para navegadores (en inglés, browser builder) de Angular realiza una búsqueda rápida para encontrar dominios listados en el cargador de imágenes. Al detectar una coincidencia que aún no ha sido declarada manualmente, inserta un enlace de preconexión en el encabezado del documento, con el atributo data-ngimg indicando que el elemento <link> fue añadido automáticamente durante el tiempo de construcción.
<head>
<!– Elementos adicionales de la sección <head> –>
<!– Enlace de preconexión al CDN de Medium, añadido automáticamente –>
<link
rel=”preconnect”
href=”https://cdn-images-1.medium.com”
data-ngimg>
</head>
Esta forma de carga especulativa indica al navegador web que debe priorizar la búsqueda DNS, la negociación TLS y el establecimiento de la conexión TCP con el servidor de imágenes que va a ser probablemente el encargado de servir el elemento LCP.
Adicionalmente, en modo de desarrollo, hay un verificador de enlaces de preconexión en tiempo de ejecución que verifica si existe la sugerencia correspondiente para optimizar imágenes marcadas con el atributo priority. Cuando el verificador no puede confirmar esto, la consola del navegador muestra una advertencia.
Advertencia en la consola del navegador activada por la falta de un enlace de preconexión de dominio para una imagen prioritaria.
De ser necesario, podemos informar al verificador de enlaces sobre dominios dedicados al desarrollo y pruebas de software. Dado que estos generalmente no requieren sugerencias de preconexión, especificarlos en el token PRECONNECT_CHECK_BLOCKLIST es una buena idea para deshabilitar advertencias innecesarias. Por defecto, las direcciones localhost, 127.0.0.1, y 0.0.0.0 están incluidas en esta lista de bloqueo.
{
provide: PRECONNECT_CHECK_BLOCKLIST,
useValue: [
‘https://dev-domain.com’,
‘https://test-domain.com’,
‘https://another-excluded-domain.com’
]
}
Cuando la aplicación de Angular es entregada empleando SSR (Representación del lado del servidor), la directiva añade un elemento de enlace de precarga al encabezado del documento. Esta sugerencia va más allá del preconnect al solicitar al navegador que inicie tanto la conexión como la descarga anticipada de la imagen, mejorando la percepción de velocidad de carga del usuario a través de la obtención prioritaria de elementos candidatos a LCP.
<head>
<!– Elementos adicionales de la sección <head> –>
<!– Enlace de precarga a la imagen prioritaria, añadido automáticamente en modo SSR –>
<link
as=”image”
href=”https://cdn-images-1.medium.com/max/640/0*hiSjMcbdqbV35wRI”
rel=”preload”
fetchpriority=”high”>
</head>
Las imágenes adaptativas tienen los atributos imagesizes e imagesrcset añadidos al elemento <link> cuando los correspondientes atributos sizes o srcset son declarados en la imagen optimizada.
<head>
<!– Elementos adicionales de la sección <head> –>
<!– Enlace de precarga a la imagen prioritaria adaptativa, añadido automáticamente en modo SSR –>
<link
as=”image”
href=”https://cdn-images-1.medium.com/max/640/0*hiSjMcbdqbV35wRI”
rel=”preload”
fetchpriority=”high”
imagesizes=”(max-width: 400px) 100vw,
(max-width: 1200px) 50vw,
100vw”
imagesrcset=”https://cdn-images-1.medium.com/max/380/0*hiSjMcbdqbV35wRI 380w,
https://cdn-images-1.medium.com/max/640/0*hiSjMcbdqbV35wRI 640w,
https://cdn-images-1.medium.com/max/1200/0*hiSjMcbdqbV35wRI 1200w,
https://cdn-images-1.medium.com/max/1920/0*hiSjMcbdqbV35wRI 1920w,
https://cdn-images-1.medium.com/max/2048/0*hiSjMcbdqbV35wRI 2048w,
https://cdn-images-1.medium.com/max/3840/0*hiSjMcbdqbV35wRI 3840w”>
</head>
También podemos dar varios pasos hacia adelante incluyendo manualmente nuestras propias reglas especulativas en un elemento <script type=”speculationrules”>, siguiendo la Speculation Rules API (que aún se encuentra en modo experimental). Éstas nos brindan un control granular sobre las estrategias de precarga y pre-representación, aprovechando el tiempo de inactividad del navegador y los recursos de red. También evitan la precarga en modos de ahorro de batería y de ahorro de datos, y ayudan a proteger la privacidad de los datos cuando la precarga ocurre entre diferentes dominios.
Incluyendo vista previa de imágenes
Angular en la versión 17.2 introdujo las vistas previas o marcadores para imágenes (en inglés, placeholders), con una mínima configuración básica que requiere solamente añadir el atributo placeholder al elemento de imagen optimizada. Cuando se especifica, la directiva solicita una miniatura de la imagen, presentándola como una vista previa difuminada para crear un efecto de transición hasta que la imagen de alta resolución esté completamente cargada.
El ancho predeterminado de la miniatura es de 30px, ajustable de ser necesario a través de la propiedad placeholderResolution en el proveedor IMAGE_CONFIG de la directiva.
https://medium.com/media/94e3278118a88f8b30fbb231c081f75d/href
La directiva NgOptimizedImage define algunas reglas CSS dentro del atributo style de su elemento anfitrión para alargar condicionalmente la miniatura de baja resolución y cubrir, como imagen de fondo, toda el área del contenedor. La propiedad CSS filter: blur(…)es aplicada de manera opcional para lograr que la imagen pixelada se difumine.
Esperando que la imagen de alta resolución se cargue después de incluir el atributo `placeholder` al elemento controlado por la directiva.Después de obtener la imagen adecuada de la lista `srcset`, los estilos temporales que ayudan con la visualización de la imagen temporal de baja resolución son eliminados del elemento.
Tengamos en cuenta que cuando el atributo placeholder está definido, pero no se proporciona un cargador de imágenes, la directiva corre el riesgo de declarar la imagen de mayor resolución como su propia vista previa, lo que lleva a transformaciones de estilo innecesarias.
En modo de desarrollo, ocurre un error en tiempo de ejecución si esta condición se cumple, aconsejándonos solucionar este problema.
La consola del navegador informando de un error en tiempo de ejecución si el atributo `placeholder` se usa con un cargador genérico.
De todos modos, la directiva ofrece una manera de mostrar un marcador de posición personalizado al usar un cargador genérico, permitiendo pasar una miniatura codificada en base64 en el atributo placeholder. Menos de 4000 caracteres debería ser suficiente para codificar una imagen de baja resolución de 20 x 20 píxeles. Cuando excedemos esta longitud se genera una advertencia en la consola del navegador en modo de desarrollo. Si la longitud de la imagen codificada supera los 10000 caracteres, se lanza un error en tiempo de ejecución para evitar un aumento no deseado en el tamaño de la plantilla HTML que puede socavar nuestros esfuerzos de mejora del rendimiento.
La consola del navegador reportando un error en tiempo de ejecución por longitudes muy grandes de imágenes codificadas en base64.
Vale la pena mencionar que el concepto de marcador de posición a nivel de imagen optimizada no está conectado con los de las vistas diferibles, ya que los sub-bloques @placeholder son visualizados hasta que sus condiciones diferidas son satisfechas. Sin embargo, podemos hacer que se complementen entre sí, como sugiere el siguiente ejemplo:
https://medium.com/media/09fef9c41be5d0a5d91cba3d305e1e96/href
Conclusión
Como hemos explorado, el uso adecuado de la directiva NgOptimizedImage no solo le permite llevar a cabo automáticamente la automatización interna que ayuda al navegador a tomar decisiones sobre la representación de imágenes, sino que también mejora el rendimiento general de la aplicación y la experiencia de usuario.
Les invito a que consulten el siguiente repositorio de GitHub con el código referenciado en este artículo, para continuar experimentando por su cuenta cómo es que Angular genera el HTML para elementos de imagen optimizadas:
Read the English version of this article on the ng-conf blog:
Automatic Adjustments in Angular’s Optimized Images
Ajustes automáticos en las imágenes optimizadas por Angular was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.