My Profile Photo

Eric Z. Casaucao


Android Developer • Clean Code Enthusiasm • In love with Melania


Cargando imágenes: parte I

Imagen en alta resolución para demostración

Photo by shontz photography on Unsplash

Lo que más me gusta de Jekyll es, sin duda, lo rápido que carga. Al ser todo contenido estático, sin procesamiento en el servidor, la única espera está en lo que tarda la transferencia de datos desde el servidor hasta el dispositivo. Y como este sitio está alojado en Firebase las garantías técnicas son exquisitas (supongo que de forma parecida a los que lo alojan en Github).

Todo el monte era orégano hasta que decidí poner la primera imagen en un post. Me surgió una pregunta bastante básica: ¿con qué resolución trabajo? Estaba claro que si ponía imágenes con bastante calidad perdería toda esa sensación de velocidad, y si añadía imágenes excesivamente optimizadas, con baja resolución y tamaño, nadie podría fijarse en los detalles ni incluso ampliarlas.

Hasta ahora, Wordpress y otros CMS me habían facilitado la vida bastante al tener plugins (Jetpack, por ejemplo) que de forma automática reescalaban la imagen y servían la más óptima. Así que, después de varios días investigando y pensando, definí completamente cuál era el problema que tenía y qué era realmente lo que quería.

¿La solución?

  • Jekyll Responsive Image, para servir la imagen más óptima.
  • LazySizes, para usar la técnica LQIP (Low Quality Image Placeholder).
  • Fullscreen modal, para permitir ampliar una imagen.

¡Esto es justo lo que necesitaba! Así que el post de hoy y los siguientes van a consistir precisamente en eso, en explicar cómo utilizar los dos primeros plugins más un poquito de JavaScript básico para construir una ventana modal y poder ampliar las imágenes.

En este post vamos a analizar el primer plugin: Jekyll Responsive Image.

Jekyll Responsive Image

Lo primero es obtener el plugin desde aquí. Lo suyo sería tener un set de imágenes, de distintas resoluciones, y que el dispositivo escogiera la que mejor se adapte a su resolución. Es decir, supongamos que tenemos las siguientes imágenes:

  • picture_1024.png
  • picture_640.png
  • picture_320.png

Los números del final indican la resolución para el ancho de la imagen. Si yo accedo al contenido desde mi PC, probablemente quiera ver la imagen picture_1024.png. Sin embargo, si entro desde mi móvil, tal vez sea mejor ver la imagen picture_320.png y así ahorro datos y mejoro la velocidad de carga; pero qué trabajoso si por cada imagen con la que trabajamos tuviéramos que reescalarla varias veces a mano.

Este plugin se encarga de hacer precisamente eso, tú trabajas con una imagen y el plugin se encarga de hacer el reescalado (configurable a gusto del consumidor) y de servir la que más se ajuste.

1. Instala el plugin

Añade a tu Gemfile el plugin:

gem 'jekyll-responsive-image'

Y añádelo a tu _config.xml:

plugins:
  - jekyll-responsive-image

2. Elige una plantilla

Esta plantilla contendrá el código HTML que se incluirá en la página renderizada. Yo utilizo una plantilla basada en srcset-resized-fallback.html, y este archivo debe ir en la carpeta _includes. Puedes echar un vistazo a algunas plantillas de ejemplo y estudiar cuál se ajusta mejor a tus necesidades.

Yo he modificado ligeramente la plantilla de ejemplo; pego su contenido por partes y lo analizamos:

{% assign largest = resized | sort: 'width' | last %}
{% assign smallest = resized | sort: 'width' | first %}
{% capture srcset %}
{% for i in resized %}
    /{ { i.path } } { { i.width } }w,
{% endfor %}
{% endcapture %}

En la variable largest se guarda el objeto imagen de mayor resolución que ha generado el plugin; la más pequeña se guarda en smallest. En srcset se guarda un string con la información necesaria para que el dispositivo elija la más óptima:

/assets/resized/hdr-120x80.jpg 120w, /assets/resized/hdr-480x320.jpg 480w, /assets/resized/hdr-640x427.jpg 640w,

Esto no es relativo al plugin, es HTML puro. Puedes aprender más en este enlace.

La siguiente parte de la plantilla es un workaround que tuve que incluir por un problema que me encontré.

{% if original.width < 640 %}
  {% assign width = '' %}
  {% capture src %}/{{ smallest.path }}{% endcapture %}
  {% capture data-src %}/{{ original.path }}{% endcapture %}
  {% capture data-srcset %}{% endcapture %}
{% else %}
  {% assign width = '100%' %}
  {% capture src %}/{{ smallest.path }}{% endcapture %}
  {% capture data-src %}/{{ largest.path }}{% endcapture %}
  {% capture data-srcset %}{{ srcset | strip_newlines }}{% endcapture %}
{% endif %}

La idea es que si yo, como redactor de un post, decido incluir una imagen de una resolución menor que el ancho de mi blog, no quiero que se muestre al 100% del ancho del blog, sino al ancho “natural” de la imagen.

El ancho de mi blog, a día de hoy, es de 640px, así que si incluyo una imagen de 320px se quedará con ese ancho, y este workaround consigue precisamente eso.

La última parte es lo que se incluirá en la página renderizada:

<img width="{{ width }}" class="fullscreen lazyload blur-up" src="{{ src }}" alt="{{ alt }}" data-src="{{ data-src }}" data-srcset="{{ data-srcset }}" fullscreen-base-src="/{{ largest.path }}" fullscreen-src="/{{ original.path }}" data-sizes="auto">

Volveremos más tarde a esta parte para analizar algunos atributos ya que, para usar el plugin, no todos los que ves son necesarios. Por ahora, todo lo que necesitamos saber es que nuestra plantilla ya funciona. Falta configurar el plugin para que el reescalado lo haga según nuestras necesidades.

3. Configuración

La configuración, como siempre, va en _config.xml:

responsive_image:
  template: _includes/srcset-resized-fallback.html
  default_quality: 80
  sizes:
    - width: 70
      quality: 20
    - width: 480
      quality: 50
    - width: 640
      quality: 80

Desgranamos:

  • template: es la ruta donde se incluirá nuestra plantilla por defecto.
  • default_quality: [0-100]. Poco que decir.
  • sizes: Aquí especificamos los distintos tamaños en los que el plugin reescalará nuestra imagen. Como el ancho de mi blog es 640px, no quiero que ninguna imagen tenga más de ese tamaño. También especificamos un ancho excesivamente pequeño (70px) para… bueno, más adelante lo veremos.

Ya tenemos todo listo. ¿Qué hemos conseguido hasta ahora? Que nuestro blog sirva imágenes responsive.

4. Uso

Usar el plugin es extremadamente sencillo:

{% responsive_image path: images/foo.png %}

images.foo.png es la ruta de la imagen original, la que incluimos con la resolución mayor, no ninguna de las que genera el plugin.

Esto tiene una parte negativa que personalmente no me gusta: si cuando redactas el blog quieres incluir una imagen, necesitas “incluir código” al mismo tiempo que escribes.

Yo utilizo Typora para redactar mis posts. Incluir una imagen es facilísimo: click derecho e insertar imagen. Typora genera el siguiente código en Kramdown:

Código para insertar una imagen con Kramdown

Sin embargo, ese código está muy lejos del que tenemos que utilizar para hacer uso del plugin. Si hubiera alguna forma de decirle a Jekyll: “ey, todo el código de Kramdown que te encuentres con ‘esta’ estructura, por favor, conviértelo al código que entienda el plugin”, pues entonces todo estaría resuelto. Afortunadamente, existe una forma: Jekyll Hooks.

La idea es crear un hook para que cuando Jekyll renderice nuestro sitio, detecte lo que es una imagen y lo sustituya por el código que entiende el plugin. La forma de hacerlo es creando un plugin de Jekyll, obviamente programado en Ruby. Yo lo he llamado: img_tag_transform.rb y hay que ubicarlo en la carpeta _plugins. Explicamos las partes más interesantes del contenido:

Jekyll::Hooks.register :posts, :pre_render do |post, payload|
  docExt = post.extname.tr('.', '')
  post.content.gsub!(/!\[(.*)\]\(([^\)]+)\)(?:{:([^}]+)})*/, '{% responsive_image path: \2 \3 alt: "\1" %}')
  post.content.gsub!('path:../', 'path: /')
  post.content.gsub!('path:/', 'path: ')
end
  • Jekyll::Hooks.register :posts, :pre_render do |post, payload|: con esta instrucción le decimos a Jekyll que registre un hook que se va a disparar justo antes de renderizar la página. ¿Cualquier página? No, solo justo antes de renderizar un post (no me interesa aplicar el plugin a todas las imágenes de mi sitio).
  • post.content.gsub!(/!\[(.*)\]\(([^\)]+)\)(?:{:([^}]+)})*/, '{% responsive_image path: \2 \3 alt: "\1" %}'): aquí reside la magia. Con una “simple” expresión regular modficamos el código de una imagen en Kramdown por el código que entiende el plugin Jekyll Responsive Image.
  • post.content.gsub!('path:../', 'path:/') y post.content.gsub!('path:/', 'path: ') sirven para reemplazar caracteres que no nos sirven del path de la imagen.

Y ahora sí, ya tendríamos nuestro sitio sirviendo imágenes responsive de forma totalmente automatizada. No tenemos que preocuparnos por nada.

Una playa de Almería

Photo by Jose Maria Cuellar on Unsplash

Aún así, todavía podríamos estar más cerca de la perfección. ¿Cómo? Aplicando LQIP para que la carga (o sensación de carga) sea cuasi inmediata. Pero eso… en los próximos posts.