Crear layouts masonry con JavaScript y MiniMasonry

Descubre como crear un layouts en forma de masonry, sin complicaciones, con JavaScript y la librería MiniMasonry.

https://spope.github.io/MiniMasonry.js/ Minimalist dependency free Masonry layout library.

En la última parte de éste escrito, he preparado un ejercicio práctico para ver cómo implementar la librería. Haz click en la siguiente imagen para ver el resultado final en una ventana nueva:

Ejercicio con MiniMasonry
Ejercicio con MiniMasonry

¿Quieres aprender a hacer este ejercicio? Saltar directamente al tutorial

Crear layouts masonry como el de Pinterest con JavaScript y MiniMasonry

Con la aparición de Pinterest allá por el 2010, se popularizó una maquetación de contenidos llamado «masonry». Este tipo de «layout» consiste, principalmente, en organizar los elementos que aparecen en pantalla, como si de ladrillos de una fachada se tratara. 

Esta forma de presentar los contenidos resulta especialmente conveniente porque aprovecha tanto el ancho como el alto del contenedor. Y Minimiza los espacios verticales y horizontales que separan cada elemento.

Pinterest dió en el clavo al disponer su contenido siguiendo este patrón. Y la prueba es que no lo han modificado desde entonces.

El diseño en “masonry” se ajusta perfectamente para una galería de imágenes de alturas dispares. Cualquier otra maquetación, hubiera generado muchos espacios en blanco y probablemente una peor experiencia de usuario. Y adicionalmente, genera en el usuario ganas de seguir haciendo scroll y cargar nuevas imágenes.

Layout masonry
Layout masonry ejemplo

Sin embargo, aplicar esta disposición en «masonry» no es tan trivial como puede parecer a simple vista. Implementarlo con CSS puede resultar complejo, especialmente hace unos años, cuando aún no se encontraba en la versión CSS3.

Así pues, para facilitar la implementación de este patrón, se crearon librerías javascript como MiniMasonry.

Antes de empezar a analizar la librería, cabe mencionar que David Desandro, creador ZDog, una de nuestras librerías favoritas, también desarrolló una solución Masonry.

Una API sencilla pero efectiva

Gracias a MiniMasonry, con unas pocas líneas de código, se puede generar un “grid” en disposición masonry con múltiples configuraciones de acabado. 

Esta librería creada por Spope, sólo ocupa 4K minificada y puede ser importada en nuestro entorno mediante el comando “npm install minimasonry –save”.

Su repositorio en Github tiene una valoración de 300 estrellas, y una media de 398 descargas a través de NPM.

El punto fuerte de esta librería es la sencillez de su API. Como suele ser común, tras importar la librería, se instancia pasando un objeto propiedades. Con tan solo 10 opciones de configuración, ya cubre la mayoría de casos que un desarrollador pueda necesitar.

La única opción de configuración es “container”. Esta propiedad puede ser una cadena de texto o un elemento HMTL. Y actuará como padre del contenido dispuesto en masonry.

Las demás propiedades hacen referencia a opciones como el tamaño de las columnas, el espaciado entre ellas, o la dirección de lectura, entre otras.

Solo expone dos métodos, “layout()” y “destroy()”. Con “layout()”, se puede forzar de nuevo la maquetación en “masonry”. Eso es especialmente conveniente cuando se carga de forma asíncrona nuevos elementos en las columnas. Como su nombre sugiere, “destroy()” dejará la maqueta como antes de instanciar MiniMasonry. Y eliminará la escucha al evento “resize”.

MiniMasonry, utiliza internamente las propiedades css llamadas “transform”. Eso significa que el navegador no tiene que recalcular el “layout”, y que adicionalmente, utiliza la GPU del dispositivo.

Por supuesto, la librería se ajusta al ancho de pantalla para ofrecer una integración “responsive”, acomodando el contenido a todos los dispositivos.

Como ya se ha comentado, el mejor referente para este tipo de maquetación es Pinterest. Sin embargo, es fácil pensar en otras aplicaciones. Podría encajar perfectamente como “grid” para un “Dashboard” de control.

Crea tu propio Pinterest

Seguidamente, vamos a implementar la librería para crear nuestro propio Pinterest. Cargaremos imágenes de tamaños aleatorios, directamente de la web Lorem Picsum. Un recurso muy interesante para obtener imágenes libres de licencias.

Para obtener una imágen aleatoria de esta web, sencillamente tenemos que construir una url como la siguiente, https://picsum.photos/{ancho}/{alto}.

Para empezar, vamos a preparar una base con HTML. Este archivo sencillamente va a cargar un título, y una “div” contenedora, con algunas imágenes cargadas.

  <body>
    <div class="title-container">MiniMasonry.js</div>
    <div class="container">
      <div><img src="https://picsum.photos/310/500" alt=""></div>
      <div><img src="https://picsum.photos/500/500" alt=""></div>
      <div><img src="https://picsum.photos/300/800" alt=""></div>
      <div><img src="https://picsum.photos/100/200" alt=""></div>
      <div><img src="https://picsum.photos/300/510" alt=""></div>
      <div><img src="https://picsum.photos/350/500" alt=""></div>
      <div><img src="https://picsum.photos/300/700" alt=""></div>
      <div><img src="https://picsum.photos/600/300" alt=""></div>
      <div><img src="https://picsum.photos/400/210" alt=""></div>
      <div><img src="https://picsum.photos/200/300" alt=""></div>
      <div><img src="https://picsum.photos/210/400" alt=""></div>
      <div><img src="https://picsum.photos/520/520" alt=""></div>
      <div><img src="https://picsum.photos/100/100" alt=""></div>
      <div><img src="https://picsum.photos/240/510" alt=""></div>
      <div><img src="https://picsum.photos/340/410" alt=""></div>
      <div><img src="https://picsum.photos/350/580" alt=""></div>
      <div><img src="https://picsum.photos/112/120" alt=""></div>
      <div><img src="https://picsum.photos/800/500" alt=""></div>
      <div><img src="https://picsum.photos/300/610" alt=""></div>
      <div><img src="https://picsum.photos/600/400" alt=""></div>
    </div>
  </body>

La parte de SCSS tampoco tiene mucha complejidad. Damos estilos generales al body. Posicionamos el título de forma fija. Y damos posiciones absolutas a los contenedores de las imágenes dentro de nuestro “.container”.

body {
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
}
.title-container {
  position: fixed;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 2rem;
  padding: 10px 20px 10px 20px;
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.2);
  border-radius: 5px;
  color: #fff;
  z-index: 1;
  background-color: #5b48d9;
  font-weight: 100;
}
.container {
  position: relative;
  div {
    position: absolute;
    img {
      display: block;
      width: 100%;
    }
  }
}

Implementar la parte de JavaScript, empieza por importar la libreria, y los estilos.

import MiniMasonry from "minimasonry";
import "./SCSS/index.scss";

A continuación, guardamos una referencia a nuestro contendor principal. E instanciamos MiniMasonry, pasandole ese contenedor.

Adicionalmente, declaramos una variable de control, para saber si se están cargando nuevas imágenes.

const container = document.querySelector(".container");
let isLoading = false;
const miniMasonry = new MiniMasonry({
  container,
});

Declaramos un conjunto de funciones. HandleOnLoadImage solo se encargará de actualizar la posicion de los elementos, mediante el método “layout()”.

const handleOnLoadImage = () => {
  miniMasonry.layout();
};

GetRandomValueInRange sencillamente devuelve un valor numérico aleatorio dentro de un rango. Por cierto, esta función ha sido sugerida por Github Copilot.

const getRandomValueInRange = (min, max) => {
  return Math.floor(Math.random() * (max - min + 1)) + min;
};

AddNewImages genera 20 nuevas imágenes, y actualiza el estado de la variable “isLoading”.

const addNewImages = () => {
  for (let i = 0; i < 20; i++) {
    const div = document.createElement("div");
    const img = document.createElement("img");
    img.src = `https://picsum.photos/${getRandomValueInRange(
      200,
      500
    )}/${getRandomValueInRange(200, 500)}`;
    img.onload = handleOnLoadImage;
    div.appendChild(img);
    container.appendChild(div);
  }
  window.setTimeout(() => {
    isLoading = false;
  }, 1000);
};

Con isScrollNearBottom, comprobamos si la barra de scroll del navegador se encuentra cerca del final.

const isScrollNearBottom = () => {
  const scrollHeight = document.documentElement.scrollHeight;
  const scrollTop = document.documentElement.scrollTop;
  const clientHeight = document.documentElement.clientHeight;
  return scrollHeight - scrollTop <= clientHeight + 100;
};

Solo nos queda escuchar el evento “scroll”. En caso de que el usuario esté cerca del final de la página, y no se estén cargando elementos en ese momento, pedimos imágenes nuevas.

window.addEventListener("scroll", () => {
  if (isScrollNearBottom() && !isLoading) {
    isLoading = true;
    addNewImages();
  }
});

Finalmente, tenemos que asegurar que las imágenes se posicionan correctamente tras cargase. Para ello, escuchamos el evento “load” en cada una, y le pasamos como función callback, handleOnLoadImage.

container.querySelectorAll("img").forEach((img) => {
  img.addEventListener("load", handleOnLoadImage);
});

JavaScript para cubrir necesidades CSS

La verdad es que no soy especialmente fan de las librerías Js que resuelven necesidades de maquetación. En mi opinión, esas funcionalidades se deberían delegar al lenguaje CSS.

En realidad, existen propuestas para ello, pero aún se encuentran en fases experimentales y no en todos los navegadores.

De modo que, mientras tanto, será mejor aprovechar librerías como MiniMasonry. Por otra parte, por aquí hemos visto librerías que resuelven necesidades de UI como por ejemplo Tagify.

Crea un sistema de tags con Tagify

Este ha sido un pequeño vistazo a la librería MiniMasonry. A continuación os dejo enlaces a más recursos, así como el ejercicio subido al repositorio de Github de “Librerías Js”:

Nos vemos pronto, un abrazo desarrolladores!

Deja un comentario