Mejorando la carga de imágenes y SVGs

El primer paso y el más importante para mejorar la velocidad de carga de un sitio son las imágenes. Si todavía no sabes bien como medir la velocidad de tu sitio, el siguiente artículo puede ayudarte:

Midiendo la velocidad de tu sitio

Si ya trabajaste optimizando las imágenes, quizás te interese leer sobre otros cambios que pueden mejorar la velocidad:

Mejorando la carga de imágenes y SVGs

Mejorando la carga del CSS

Imágenes

Formato

Basándonos en pruebas que fuimos haciendo en nuestras tiendas de Tiendanube, descubrimos que si bien el formato PNG se ve con una buena calidad puede generar un peso innecesario, el cual se puede reducir hasta 10 veces cuando cambiamos al formato JPG. Claro está que si necesitás que la imagen tenga el canal alpha (es decir que use transparencia), JPG entonces no va a poder ser una opción, pero es importante tenerlo en el radar.

Producto de la tienda Unibow, una Tienda Nube donde la imagen pasó de pesar 2,5mb a 181 kb.

En el ejemplo de Unibow, el tiempo de carga de las imágenes luego de modificar el formato y comprimir usando herramientas como TinyPNG ¡Bajó de los casi 8.67 segundos a 2.07, son 6 segundos menos!

Arriba el antes y debajo el después del cambio.

WebP

Como vimos en el punto anterior, aún podemos mejorar el tiempo de respuesta de nuestra tienda utilizando formatos de próxima generación. WebP es un formato de imagen moderno desarrollado por Google, que proporciona una compresión superior. Es por esto que en Tiendanube implementamos la conversión automática en formato WEBP para los banners y sliders. 

¿Cómo uso WebP en mi tienda?

Para poder usar los distintos tamaños de imágenes y WebP, necesitas tener tu código actualizado. Si no abriste FTP, no tenés que hacer nada, solo tené en cuenta subir imágenes en formato jpg o png. De lo contrario necesitamos que uses en los banners la mayor cantidad de tamaños posibles. Para hacer esto hay que aplicar la extensión `settings_image_url()` indicando el valor de la imagen deseada.

Por ejemplo, si nuestra imagen se compone así:

<img data-src="{{ slide.image | static_url }}" class="slider-image blur-up swiper-lazy" data-sizes="auto"/>

La podemos cambiar así:

<img data-srcset='{{ slide.image | static_url | settings_image_url('large') }} 480w, {{ slide.image | static_url | settings_image_url('huge') }} 640w, {{ slide.image | static_url | settings_image_url('original') }} 1024w, {{ slide.image | static_url | settings_image_url('1080p') }} 1920w' data-sizes="auto" />

Tamaños disponibles

Tamaño
Ancho máximo
tiny
50px
thumb100px
small240px
medium320px
large480px
huge640px
original1024px
xlarge1400px
1080p1920px

Tené en cuenta que siempre respetamos la relación de aspecto de las imágenes, entonces el alto va a ser variable. 

Para evitar pérdidas de calidad sólo achicamos las imágenes y nunca las agrandamos.

SRCSET

Otro cambio clave es la incorporación de srcset, ¿Qué es esto? Básicamente imágenes responsive a través de un atributo de HTML.

Agregando el atributo srcset además del src dentro del tag img, podés pasarle varias urls de la misma imagen en distintos tamaños y el navegador al cargar se va a encargar de cargar la imagen mas adecuada en base a por ejemplo el ancho de la pantalla.


En las tiendas usamos la implementación basada en la unidad “w”, a la cual le pasamos el ancho de cada imagen que tenemos. Hay varias formas de usar srcset, te invito a leer más sobre las distintas posibilidad es que ofrece este atributo.

Te dejo un ejemplo de como se ve el código juntando lazyload + srcset para una imagen de producto:

<div style="padding-bottom: {{ product.featured_image.dimensions['height'] / product.featured_image.dimensions['width'] * 100}}%;">
    <a href="{{ product_url_with_selected_variant }}" title="{{ product.name }}">
        <img alt="{{ product.featured_image.alt }}" data-sizes="auto" src="{{ product.featured_image | product_image_url('tiny')}}" data-src="{{ product.featured_image | product_image_url('small')}} 240w, {{ product.featured_image | product_image_url('medium')}} 320w" class="lazyload item-image img-absolute blur-up" />
    </a>
</div>

Así sería para un banner:

<img src=“{{ slide.image | static_url | settings_image_url(‘tiny’) }}”
    data-srcset=‘{{ slide.image | static_url | settings_image_url(‘large’) }} 480w, {{ slide.image | static_url | settings_image_url(‘huge’) }} 640w, {{ slide.image | static_url | settings_image_url(‘original’) }} 1024w, {{ slide.image | static_url | settings_image_url(‘1080p’) }} 1920w’
    class=“slider-image blur-up swiper-lazy” data-sizes=“auto” alt=“{{ ‘Carrusel’ | translate }}“/>

LazyLoad

¿Para qué cargar todas las imágenes si el usuario todavía no necesita verlas? Pueden lograr esto usando la técnica de “LazyLoad” que básicamente carga las imágenes de forma progresiva a medida que el usuario va haciendo scroll o abre una pantalla o popup que tiene estas imágenes que no fueron cargadas.

De esta forma se ahorra una demora enorme hasta que el documento termina de cargar todas las imágenes, ¿Para qué llamar a imágenes que están en el footer o en un popup apenas carga la página?.

Hay decenas de plugins de JS para usar LazyLoad, en Tiendanube usamos lazysizes. Algunos puntos sobre lazysizes:

  • No depende de Jquery, importante para cargar lo mínimo de código bloqueante (Javascript o CSS).
  • Es fácil de usar, simplemente agregando la class “lazyload” a la img, luego al atributo src se le pasa la url de un placeholder (o puede ser la misma imagen en baja calidad) y finalmente usando el atributo data-src (o data-srcset si estás necesitan srcset) con la url de la imagen final en buena calidad. Una vez que cargue la imagen, la class “lazyload” va a pasar a ser “lazyloaded”, esto podés usarlo para hacer cambios CSS una vez que se terminó la carga.
  • Si vas a usar srcset es importante considerar las extensiones respimg(para que los browsers vintage como IE 9 y 11, lo odio con todo mi corazón) y bgset en caso que necesiten usar una imagen como background-image sumado a srcset.
  • Otro punto interesante es que el plugin no va a cargar las imágenes que tengan o estén englobadas por un elemento con un "display:none;".
  • Si necesitás exprimir más el jugo del plugin podés darle una ojeada a las distintas extensiones que tiene

Por otra parte, una de las métricas de velocidad de carga de páginas web es el LCP (Largest Contentful Paint), que mide el tiempo en que se muestra el contenido visualmente más grande. Por esto, no es recomendable aplicar lazyload a las imágenes que deberían verse apenas carga la página (también llamado "above the fold") porque lo más probable es que demore el tiempo de LCP, bajando la puntuación general de performance. 

Por ejemplo, en mobile, uno de los elementos más grandes que frecuentemente vemos presente es el carrusel de imágenes, por lo que para la primera imagen visible se sugiere dejar el “srcset” del contenido final.

Placeholders

Tenés LazyLoad, ¡Genial! Pero ¿Qué pasa mientras no se muestra la imagen final?. 

Se pueden hacer varias cosas hasta tener la imagen final, en nuestras tiendas lo que hicimos fue aplicar la técnica de LQIP (Low Quality Image Placeholders) cargando la imagen en un tamaño muy chico con un efecto blur hasta que lazy load muestre la imagen final. 

Para aplicar el LQIP, hay que aplicar al src del tag img la imagen en baja calidad y peso (en Tienda Nube tenemos varias versiones de la misma imagen, las más chica con 50px de ancho, los tamaños que usamos son: tiny de 50px de ancho, thumb de 100px, small de 240px, medium de 320px, large de 480px, huge de 640px y original de 1024px) con un CSS que le da width al 100% de su contenedor y luego cuando la imagen es “lazyloadeada” se reemplaza por la imagen en buena calidad. En el medio se pueden usar transiciones con CSS, nosotros aplicamos un efecto de blur animado.

El código queda más o menos así:

<img alt=”{{ product.featured_image.alt }}” data-sizes=”auto” src=”{{ product.featured_image | product_image_url(‘tiny’)}}” data-src=”{{ product.featured_image | product_image_url }}” class=”lazyload item-image img-absolute blur-up” />

Donde se puede ver que estamos usando la class “lazyload” para usar lazysizes, la class blur-up para la transición de “imagen blureada” a “imagen final” y los atributos src para la imagen placeholder en baja calidad (tiny) y data-src para la imagen final (más adelante vamos a usar el mismo ejemplo con srcset).

El CSS para la transición es el siguiente:

.blur-up {
  -webkit-filter: blur(6px);
  filter: blur(6px);
  transition: filter .5s, -webkit-filter .5s;
}
.blur-up.lazyloaded {
  -webkit-filter: blur(0);
  filter: blur(0);
}

Usando la class “lazyloaded” puede ayudar para incluso mostrar un icono y luego ocultarlo una vez que se cargó la imagen.

Aún usando el código anterior el resultado no sigue siendo el mejor, vas a tener saltos visuales entre qué mostramos la imagen chica (o puede ser un icono placeholder) y la imagen final. Para evitar esto necesitás saber el espacio que va a ocupar la imagen antes de esta sea cargada y para esto necesitás conocer las dimensiones de alto y ancho desde el backend.
Con esta información vas a tener que hacer lo siguiente:

<div style="padding-bottom: {{ product.featured_image.dimensions['height'] / product.featured_image.dimensions['width'] * 100}}%;">
    <a href="{{ product_url_with_selected_variant }}" title="{{ product.name }}">
        <img alt="{{ product.featured_image.alt }}" data-sizes="auto" src="{{ product.featured_image | product_image_url('tiny')}}" data-src="{{ product.featured_image | product_image_url }}" class="lazyload item-image img-absolute blur-up" />
    </a>
</div>

Una vez cargado en el navegador se va a ver así:


Vas a notar dos cosas:

  • Hay un estilo inline sobre el contenedor de la imagen, que básicamente es la cuenta: alto/ancho*100. Está cuenta les va a dar un porcentaje que van a usar como un padding-bottom , el cual va a “empujar” el espacio que ocupará la imagen final con la relación de aspecto correcta (muy útil también en grillas tipo Pinterest). La cuenta va a dar algo como padding-bottom: 133.68146214099%; .
  • Por otro lado necesitás que la imagen se ubique bien dentro del contenedor sin que sea empujada por ese padding. Para eso usen la class img-absolute con el siguiente CSS:
.img-absolute {
  position: absolute;
  left: 0;
  width: 100%;
  height: auto;
  vertical-align: middle;
  text-indent: -9999px;
  z-index: 1;
}

SVGs

El SVG también es un formato gráfico como la imagen y mejora considerablemente la velocidad de carga ya que es un vector que no demora tanto en “ser pintado” por el navegador como una imagen clásica donde se tiene que pintar/cargar pixel por pixel.

En nuestro caso usamos SVGs para cargar todos los iconos del sitio, de hecho antes usábamos Font Awesome pero lo removimos y pasamos todo a SVGs para no ahorrar el llamado a un CSS tipográfico con cientos de íconos cuando solo necesitábamos algunos.

Para usar los SVGs lo que tenés que hacer es:

  • Crear el SVG en un software como Illustrator o el que te sientas cómodo, o descargá un SVG hecho.
  • Desde illustrator vas a la opción de “guardar como” y elegís el formato SVG Tiny 1.1.


  • Luego elegís la opción de código SVG.


  • Copiás ese código y lo pegás en el sitio SVGOMG que te va a ayudar a comprimirlo aún más. Hacé click en el icono de “copiar”.

Ya tenés el código SVG listo para ser pegado donde quieran, es importante que este inline en la maqueta para evitar un pedido del navegador innecesario. En Tienda Nube generamos un archivo para cada SVG y luego lo importamos con Twig usando un include que básicamente hace lo mismo que si lo pegaran inline en el HTML.

El archivo creado es un .tpl que usa el atributo {{ svg_custom_class }} .

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 38 32.5"><path d="M18.5,28.5a4,4,0,1,1-4-4A4,4,0,0,1,18.5,28.5Zm11-4a4,4,0,1,0,4,4A4,4,0,0,0,29.5,24.5Zm5.44-3.44,3-13A2.5,2.5,0,0,0,35.5,5H16a2.5,2.5,0,0,0,0,5H32.36l-1.85,8h-17L9.94,2A2.49,2.49,0,0,0,7.5,0h-5a2.5,2.5,0,0,0,0,5h3L9.06,21a2.49,2.49,0,0,0,2.44,2h21A2.51,2.51,0,0,0,34.94,21.06Z"/></svg>

Este atributo lo usamos para pasarle la class que necesitemos al incluirlo:

{% include "snipplets/svg/cart.tpl" with {svg_custom_class: "icon-inline icon-2x"} %}

A su vez usamos un CSS que define distintos tamaños para los SVGs, muy similar a lo que usa Font Awesome.

.icon-inline {
  display: inline-block;
  font-size: inherit;
  height: 1em;
  overflow: visible;
  vertical-align: -.125em;
}


.icon-xs {
  font-size: .75em;
}
.icon-sm {
  font-size: .875em; 
}
.icon-lg {
  font-size: 1.33333em;
  line-height: .75em;
  vertical-align: -.0667em; 
}
.icon-2x {
  font-size: 2em;  
}
.icon-3x {
  font-size: 3em; 
}
.icon-4x {
  font-size: 4em;  
}
.icon-5x {
  font-size: 5em;  
}
.icon-6x {
  font-size: 6em;  
}
.icon-7x {
  font-size: 7em; 
}
.icon-8x {
  font-size: 8em;  
}
.icon-9x {
  font-size: 9em;  
}


.icon-inline.icon-lg{
  vertical-align: -.225em
}
.icon-inline.icon-w {
  text-align: center;
  width: 1.25em
}
.icon-inline.icon-w-1{
  width:.0625em
}
.icon-inline.icon-w-2{
  width:.125em
}
.icon-inline.icon-w-3{
  width:.1875em
}
.icon-inline.icon-w-4{
  width:.25em
}
.icon-inline.icon-w-5{
  width:.3125em
}
.icon-inline.icon-w-6{
  width:.375em
}
.icon-inline.icon-w-7{
  width:.4375em
}
.icon-inline.icon-w-8{
  width:.5em
}
.icon-inline.icon-w-9{
  width:.5625em
}
.icon-inline.icon-w-10{
  width:.625em
}
.icon-inline.icon-w-11{
  width:.6875em
}
.icon-inline.icon-w-12{
  width:.75em
}
.icon-inline.icon-w-13{
  width:.8125em
}
.icon-inline.icon-w-14{
  width:.875em
}
.icon-inline.icon-w-15{
  width:.9375em
}
.icon-inline.icon-w-16{
  width:1em
}
.icon-inline.icon-w-17{
  width:1.0625em
}
.icon-inline.icon-w-18{
  width:1.125em
}
.icon-inline.icon-w-19{
  width:1.1875em
}
.icon-inline.icon-w-20{
  width:1.25em
}
.icon-spin{
  -webkit-animation:icon-spin .5s infinite linear;
  animation:icon-spin .5s infinite linear
}
@-webkit-keyframes icon-spin {
  0% {
    -webkit-transform: rotate(0);
    transform: rotate(0)
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg)
  }
}


@keyframes icon-spin {
  0% {
    -webkit-transform: rotate(0);
    transform: rotate(0)
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg)
  }
}

¿Qué sigue?

Si ya pudiste optimizar todo lo que tiene que ver con imágenes te recomiendo seguir leyendo los cambios que aplicamos sobre los otros dos frentes para mejorar la velocidad:

Mejorando la carga del Javascript

Mejorando la carga del CSS