Cuando estaba migrando mi sitio de WordPress a GoHugo quería aprovechar todo el trabajo que implicaba la migración para asegurarme también de optimizar las imágenes.
Lo que necesitaba era:
- Reducir el tamaño de la imagen original conservando la calidad
- Cargar las imágenes de manera perezosa con el atributo
loading
enlazy
de HTML - Ofrecer imágenes en distintos tamaños para cada pantalla. Es decir, ofrecer una imagen más pequeña para móviles y una más grande para dispositivos de escritorio
- Usar
picture
ysource
De este modo mi blog iba a cargar más rápido. Al final terminé creando un shortcode que me permite incrustar imágenes cumpliendo con todas las características que acabo de mencionar.
No creo que sea el primero al que se le haya ocurrido esto, pero igualmente te vengo a compartir cómo solucioné este problema a través de un shortcode propio de Hugo.
Creando shortcode de GoHugo para imágenes
Lo único que tenemos que hacer es leer la imagen que se quiere insertar, redimensionarla
a varios tamaños y crear el picture
con los source
necesarios.
Decidí ofrecer mis imágenes en webp y jpg. Pero bueno, comencemos con lo primero. Para empezar me he asegurado de que el procesamiento solo se haga en producción, así al momento de estar previsualizando no tengo que esperar mucho tiempo, ya que recuerda que el procesamiento de imágenes es muy pesado.
{{ $src := .Get "src" }}
{{ $alt := .Get "alt" | default "" }}
{{ $loading := .Get "loading" | default "lazy" }}
{{ $imagenOriginal := .Page.Resources.GetMatch $src }}
{{ if not $imagenOriginal }}
{{ errorf "optimized_image: recurso %q no encontrado en el archivo: %s" $src .Page.Path }}
{{ end }}
{{if not hugo.IsProduction}}
<img src="{{$imagenOriginal.RelPermalink}}" alt="{{$alt}}">
<strong>Esto no debería aparecer en producción. Si lo hace, revisa optimized_image</strong>
En caso de que estemos en producción entonces comienzo definiendo un arreglo de medidas. Estas medidas se me han ocurrido a mí y puede que en tu caso quieras modificarlas dependiendo de los tamaños que quieras tener.
También defino una cadena que va a tener el atributo que colocaré al srcset
del source
:
{{ $medidas := slice 320 600 800 1000}}
{{ $cadenaSrcSet := "" }}
{{ $cadenaSrcSetJpeg := "" }}
Recorro mis medidas y dentro de dicho recorrido:
- Reviso que la imagen mida más que la medida actual para redimensionarla, ya que por ejemplo, si tuviera una imagen de 600 de ancho no vale la pena redimensionarla a 800 ni a 1000
- Redimensiono la imagen al ancho actual según el slice. Es decir, en el primer paso la redimensionaré a 320
- Aquí aprovecho para reducir la calidad (q75) y convertir a webp. La redimensión se hace con el algoritmo Lanczos
- Hago lo mismo para la imagen JPG
- Concateno el src a
$cadenaSrcSet
y$cadenaSrcSetJpeg
ya que recuerda que elsrcset
es algo comohttps://example.com/url_imagen.webp 320w
Todo eso está en el siguiente código
{{if lt $medida $imagenOriginal.Width}}
{{ $imagenWebp := $imagenOriginal.Resize (printf "%dx q75 webp Lanczos" $medida) }}
{{ $imagenJpeg := $imagenOriginal.Resize (printf "%dx q75 jpeg Lanczos" $medida) }}
{{$cadenaSrcSet = printf "%s %s %dw," $cadenaSrcSet $imagenWebp.RelPermalink $imagenWebp.Width}}
{{$cadenaSrcSetJpeg = printf "%s %s %dw," $cadenaSrcSetJpeg $imagenJpeg.RelPermalink $imagenJpeg.Width}}
Y finalmente, ya fuera del recorrido del slice, añado la imagen en su tamaño original:
{{$imagenOriginalWebp := $imagenOriginal.Resize (printf "%dx q75 webp Lanczos" $imagenOriginal.Width)}}
{{$imagenOriginalJpeg := $imagenOriginal.Resize (printf "%dx q75 jpeg Lanczos" $imagenOriginal.Width)}}
{{$cadenaSrcSet = printf "%s %s %dw" $cadenaSrcSet $imagenOriginalWebp.RelPermalink $imagenOriginalWebp.Width}}
{{$cadenaSrcSetJpeg = printf "%s %s %dw" $cadenaSrcSetJpeg $imagenOriginalJpeg.RelPermalink $imagenOriginalJpeg.Width}}
Las últimas líneas son para definir el atributo sizes
que se calcula de acuerdo a tu CSS (hablaré de eso más adelante). El resto de código queda así:
{{$sizes := "(max-width: 768px) 91.25vw, 720px"}}
<picture>
<source type="image/webp" srcset="{{$cadenaSrcSet}}" sizes="{{$sizes}}">
<source type="image/jpeg" srcset="{{$cadenaSrcSetJpeg}}" sizes="{{$sizes}}">
<img src="{{$imagenOriginalJpeg.RelPermalink}}" alt="{{$alt}}" width="{{$imagenOriginalJpeg.Width}}"
height="{{$imagenOriginalJpeg.Height}}" loading="{{$loading}}" />
</picture>
{{end}}
Y el código completo del shortcode queda así:
{{ $src := .Get "src" }}
{{ $alt := .Get "alt" | default "" }}
{{ $loading := .Get "loading" | default "lazy" }}
{{ $imagenOriginal := .Page.Resources.GetMatch $src }}
{{ if not $imagenOriginal }}
{{ errorf "optimized_image: recurso %q no encontrado en el archivo: %s" $src .Page.Path }}
{{ end }}
{{if not hugo.IsProduction}}
<img src="{{$imagenOriginal.RelPermalink}}" alt="{{$alt}}">
<strong>Esto no debería aparecer en producción. Si lo hace, revisa optimized_image</strong>
{{else}}
{{ $medidas := slice 320 600 800 1000}}
{{ $cadenaSrcSet := "" }}
{{ $cadenaSrcSetJpeg := "" }}
{{ range $medida := $medidas }}
{{if lt $medida $imagenOriginal.Width}}
{{ $imagenWebp := $imagenOriginal.Resize (printf "%dx q75 webp Lanczos" $medida) }}
{{ $imagenJpeg := $imagenOriginal.Resize (printf "%dx q75 jpeg Lanczos" $medida) }}
{{$cadenaSrcSet = printf "%s %s %dw," $cadenaSrcSet $imagenWebp.RelPermalink $imagenWebp.Width}}
{{$cadenaSrcSetJpeg = printf "%s %s %dw," $cadenaSrcSetJpeg $imagenJpeg.RelPermalink $imagenJpeg.Width}}
{{end}}
{{ end }}
{{$imagenOriginalWebp := $imagenOriginal.Resize (printf "%dx q75 webp Lanczos" $imagenOriginal.Width)}}
{{$imagenOriginalJpeg := $imagenOriginal.Resize (printf "%dx q75 jpeg Lanczos" $imagenOriginal.Width)}}
{{$cadenaSrcSet = printf "%s %s %dw" $cadenaSrcSet $imagenOriginalWebp.RelPermalink $imagenOriginalWebp.Width}}
{{$cadenaSrcSetJpeg = printf "%s %s %dw" $cadenaSrcSetJpeg $imagenOriginalJpeg.RelPermalink $imagenOriginalJpeg.Width}}
{{$sizes := "(max-width: 768px) 91.25vw, 720px"}}
<picture>
<source type="image/webp" srcset="{{$cadenaSrcSet}}" sizes="{{$sizes}}">
<source type="image/jpeg" srcset="{{$cadenaSrcSetJpeg}}" sizes="{{$sizes}}">
<img src="{{$imagenOriginalJpeg.RelPermalink}}" alt="{{$alt}}" width="{{$imagenOriginalJpeg.Width}}"
height="{{$imagenOriginalJpeg.Height}}" loading="{{$loading}}" />
</picture>
{{end}}
Por cierto, además del source para webp y jpeg también he incluido una imagen fallback en caso de que el navegador no soporte source
Lazy loading
Fíjate en que en el ejemplo anterior basta con poner el atributo loading
en lazy
y el
navegador se encargará del resto
Modo de uso
Ya está liste para que lo uses, solo no te olvides de configurar el atributo sizes
con la variable $sizes
.
En mi caso lo he colocado en layouts/_shortcodes/optimized_image.html y lo uso así:
{{<optimized_image src="ubicación_imagen_relativa_al_markdown.extensión" alt="Aquí el alt">}}
Mi contenido está dividido en carpetas y mi Markdown se llama index.md. Basta con colocar las imágenes en la misma carpeta para que sean hermanas de index.md y puedo incluirlas simplemente escribiendo su nombre completo.
Cuando uses ese shortcode el HTML resultante será algo como:
<picture>
<source type=image/webp
srcset="/blog/posts/migrando-wordpress-hugo-ubuntu-rocky/%C3%9Altima%20captura%20de%20pantalla%20antiguo%20servidor%20Ubuntu_hu_bf82a67e70042c44.webp 320w, /blog/posts/migrando-wordpress-hugo-ubuntu-rocky/%C3%9Altima%20captura%20de%20pantalla%20antiguo%20servidor%20Ubuntu_hu_e40f06e6e074e9c0.webp 600w, /blog/posts/migrando-wordpress-hugo-ubuntu-rocky/%C3%9Altima%20captura%20de%20pantalla%20antiguo%20servidor%20Ubuntu_hu_3d2e92e90c43105.webp 800w, /blog/posts/migrando-wordpress-hugo-ubuntu-rocky/%C3%9Altima%20captura%20de%20pantalla%20antiguo%20servidor%20Ubuntu_hu_fba692a1cadb9744.webp 922w"
sizes="(max-width: 768px) 91.25vw, 720px">
<source type=image/jpeg
srcset="/blog/posts/migrando-wordpress-hugo-ubuntu-rocky/%C3%9Altima%20captura%20de%20pantalla%20antiguo%20servidor%20Ubuntu_hu_7065c85d7c251805.jpg 320w, /blog/posts/migrando-wordpress-hugo-ubuntu-rocky/%C3%9Altima%20captura%20de%20pantalla%20antiguo%20servidor%20Ubuntu_hu_c6b08d0dc77de7ef.jpg 600w, /blog/posts/migrando-wordpress-hugo-ubuntu-rocky/%C3%9Altima%20captura%20de%20pantalla%20antiguo%20servidor%20Ubuntu_hu_cd434a2830d05217.jpg 800w, /blog/posts/migrando-wordpress-hugo-ubuntu-rocky/%C3%9Altima%20captura%20de%20pantalla%20antiguo%20servidor%20Ubuntu_hu_8afc0da0cfaf148f.jpg 922w"
sizes="(max-width: 768px) 91.25vw, 720px">
<img src=/blog/posts/migrando-wordpress-hugo-ubuntu-rocky/%C3%9Altima%20captura%20de%20pantalla%20antiguo%20servidor%20Ubuntu_hu_8afc0da0cfaf148f.jpg
alt="Captura que muestra el inicio de sesión exitoso a un servidor Ubuntu" width=922 height=696 loading=lazy>
</picture>
Sobre el atributo sizes
Este atributo debe ser calculado de acuerdo al tamaño porcentual del ancho total que ocupan las imágenes en cada viewport. Puedes usar
vw
y px
.
Te dejo un video de YouTube para que complementes esta información y entiendas el atributo: