Cómo construir un diseño con miniaturas filtrables usando CSS Grid, Flexbox y JavaScript
Spanish (Español) translation by Steven (you can also view the original English article)
En este tutorial, vamos a tomar un montón de fotos y las convertiremos en un diseño de miniatura filtrable. ¡Combinaremos todas las últimas cosas CSS (CSS Grid, flexbox y variables CSS) junto con algunos JavaScript personalizados para construir una demostración increíble!
Estaremos creando esto:
¡Asegúrate de comprobar la demostración en una pantalla grande (>900px) porque en ese punto la magia sucede! Sin más preámbulos, ¡toma una taza de café y empecemos!
1. Comienza con el marcado de página
Comenzaremos con un .container
que contiene el elemento .toolbar
y una lista de fotos:
<div class="container"> <div class="toolbar">...</div> <ol class="image-list grid-view">...</ol> </div>
El diseño de la barra de herramientas tendrá este aspecto:

En su interior colocaremos dos elementos:
- El cuadro de búsqueda que nos permite buscar una foto específica
- una lista con tres opciones que determinan el diseño de miniaturas. De forma predeterminada, las fotos aparecen en la vista de cuadrícula, pero podemos cambiar a la vista de tipo lista haciendo clic en el icono en la esquina derecha. Además, cada vez que estamos en la vista de cuadrícula, tenemos la opción de cambiar el número de fotos que aparecen por fila. Para ello, usaremos un control deslizante de tipo rango.
Aquí está el marcado asociado para todo eso:
<div class="search-wrapper"> <input type="search" placeholder="Search for photos"> <div class="counter"> Total photos: <span>12</span> </div> </div> <ul class="view-options"> <li class="zoom"> <input type="range" min="180" max="380" value="280"> </li> <li class="show-grid active"> <button disabled> <img src="IMG_SRC" alt="grid view"> </button> </li> <li class="show-list"> <button> <img src="IMG_SRC" alt="list view"> </button> </li> </ul>
Dentro de la lista de imágenes colocaremos doce fotos de Unsplash. Cada foto viene con su descripción, así como el nombre de su propietario. Esto es lo que se verá con un poco de estilo básico (que llegaremos a poco):

Aquí está el marcado para una sola foto:
<li> <figure> <img src="IMG_SRC" alt=""> <figcaption> <p>...</p> <p>...</p> </figcaption> </figure> </li> <!-- 11 list items here -->
Es importante tener en cuenta que la lista de imágenes siempre contendrá la clase image-list
. Además, también recibirá la clase grid-view
o list-view
como esta: <ol class="image-list grid-view">...</ol>
Su segunda clase dependerá de la vista de diseño seleccionada por el usuario. Más sobre eso en las próximas secciones.
2. Definir algunos estilos básicos
Primero configuramos algunas variables CSS y algunos estilos de restablecimiento:
:root { --black: #1a1a1a; --white: #fff; --gray: #ccc; --darkgreen: #18846C; --lightbrown: antiquewhite; --darkblack: rgba(0,0,0,0.8); --minRangeValue: 280px; } * { margin: 0; padding: 0; outline: none; border: none; } button { cursor: pointer; background: none; } img { display: block; max-width: 100%; height: auto; } ol, ul { list-style: none; } a { color: inherit; }
Lo más importante, presta atención al valor de la variable minRangeValue
. Su valor (280px) coincide con el valor predeterminado del control deslizante de tipo rango.
Recuerda el marcado para nuestro control deslizante: <input type="range" min="180" max="380" value="280">
. Más adelante usaremos ese valor para establecer el ancho mínimo de nuestras fotos.
Nota: para simplificar no voy a pasar a través de todas las reglas CSS en el tutorial. Puedes comprobar el resto de ellos haciendo clic en la pestaña CSS del proyecto de demostración.
Estilo de la barra de herramientas
Como siguiente paso, aplicaremos estilo a la barra de herramientas. Estos son los puntos clave con respecto a este elemento:
- Utilizamos flexbox para diseñar su contenido.
- Cada vez que un usuario selecciona un diseño (cuadrícula o lista), el botón correspondiente marca como activo y recibe un borde verde oscuro. Además, lo deshabilitamos.
- El control deslizante de rango solo aparece cuando la vista de cuadrícula está activa y para las pantallas que tienen un ancho mínimo de 901px.
Las partes importantes de los estilos correspondientes se muestran a continuación:
/*CUSTOM VARIABLES HERE*/ .toolbar { display: flex; justify-content: space-between; align-items: center; } .view-options { display: flex; align-items: center; } .view-options li:not(:last-child) { margin-right: 1.2rem; } .view-options button { padding: 2px; border: 3px solid transparent; } .view-options .active button { border-color: var(--darkgreen); } @media screen and (max-width: 900px) { .toolbar input[type="range"] { display: none; } }
Estilo de la lista de imágenes
Como ya se ha mencionado, el diseño de la lista de imágenes dependerá del diseño seleccionado por el usuario. En cualquier caso, aprovecharemos el CSS Grid para crear este diseño.
Antes de hacerlo, vamos a aplicar algunos estilos genéricos a la lista de imágenes:
/*CUSTOM VARIABLES HERE*/ .image-list { margin: 3rem 0; } .image-list li { background: var(--lightbrown); background: var(--darkblack); } .image-list img { background: #e6e6e6; } .image-list p:first-child { font-weight: bold; font-size: 1.15rem; } .image-list p:last-child { margin-top: 0.5rem; }
En la vista de cuadrícula, los elementos de las listas se dividirán en columnas/celdas repetitivas con espacio entre ellos:
El número de elementos que aparecerán por fila depende del tamaño de la pantalla. Inicialmente, cada elemento tendrá un ancho mínimo de 280px y un ancho máximo que coincida con el ancho de su contenedor. Pero como veremos más adelante, agregaremos un poco de interactividad y daremos a los usuarios la opción de modificar el ancho mínimo. Por este motivo, no codificaremos su valor de forma rígida, sino que lo almacenaremos en la variable minRangeValue
.
Somos capaces de producir este diseño verdaderamente responsivo combinando la función CSS minmax()
con CSS Grid. Así es como se traducen los requisitos antes mencionados en términos de estilos:
:root { --minRangeValue: 280px; } .grid-view { display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(var(--minRangeValue), 1fr)); }
En la vista de tipo lista, los elementos de lista tendrán el comportamiento de nivel de bloque predeterminado:

Dentro de ellos, la imagen tendrá un ancho fijo de 150px y su descripción cubrirá el resto del espacio disponible (1fr). Además, ambos elementos se centradon verticalmente con un espacio entre ellos.
Los estilos relacionados:
.list-view li + li { margin-top: 1.5rem; } .list-view figure { display: grid; grid-gap: 1.5rem; grid-template-columns: 150px 1fr; align-items: center; }
Para entender más sobre cómo funciona minmax()
con el diseño de la cuadrícula CSS, aquí está un tutorial para principiantes:
3. Alternar entre las vistas de lista y cuadrícula
Cada vez que un usuario hace clic en el patrón deseado para la presentación de contenido, hacemos dos cosas:
- Agregar un borde verde oscuro al botón activo.
- Comprobar qué tipo de vista está seleccionada por el usuario.
Si el usuario selecciona la vista de cuadrícula, mostramos el control deslizante de tipo rango y nos aseguramos de que la lista de imágenes contenga la clase grid-view
y no la clase list-view
.
Por otro lado, si el usuario selecciona la vista de lista, ocultamos el control deslizante de tipo rango y nos aseguramos de que la lista de imágenes contenga la clase list-view
y no la clase grid-view
.
El código JavaScript requerido:
const imageList = document.querySelector(".image-list"); const btns = document.querySelectorAll(".view-options button"); const imageListItems = document.querySelectorAll(".image-list li"); const active = "active"; const listView = "list-view"; const gridView = "grid-view"; const dNone = "d-none"; for (const btn of btns) { btn.addEventListener("click", function() { const parent = this.parentElement; document.querySelector(".view-options .active").classList.remove(active); parent.classList.add(active); this.disabled = true; document.querySelector('.view-options [class^="show-"]:not(.active) button').disabled = false; if (parent.classList.contains("show-list")) { parent.previousElementSibling.previousElementSibling.classList.add(dNone); imageList.classList.remove(gridView); imageList.classList.add(listView); } else { parent.previousElementSibling.classList.remove(dNone); imageList.classList.remove(listView); imageList.classList.add(gridView); } }); }
4. Actualizar variables CSS a través de JavaScript
Ya hemos dicho que el valor inicial del control deslizante de tipo rango es 280px y coincide con el valor de la variable minRangeValue
. Además, su valor mínimo es 180px mientras que su máximo es 380px.
Necesitamos realizar un seguimiento de los cambios en el valor del control deslizante y actualizar la variable minRangeValue
en consecuencia. Esto hará que nuestro diseño de tipo vista de cuadrícula sea flexible, ya que cada fila no contendrá un número fijo de columnas.
El código JavaScript que hará el truco hace uso del evento input
:
const rangeInput = document.querySelector('input[type = "range"]'); rangeInput.addEventListener("input", function() { document.documentElement.style.setProperty("--minRangeValue", `${this.value}px`); });
Para entender este código, abre las herramientas del navegador y actualiza el valor del control deslizante. Observarás que el elemento html
recibe un estilo en línea que sobrescribe el valor de la propiedad establecido a través de CSS:

5. Crea la funcionalidad de búsqueda
Actualmente sólo tenemos doce imágenes, pero cada imagen es un escenario donde tenemos decenas de imágenes. En tal caso, sería muy bueno si los usuarios tuvieran la capacidad de buscar fotos específicas.
Por lo tanto, vamos a continuar y construiremos un componente de búsqueda personalizado como este:

Ten en cuenta que solo funcionará para las descripciones de las imágenes:

Como primer paso, hacemos lo siguiente:
- Recorrer todas las fotos.
- Para cada foto que encontramos, inicializamos un literal de objeto con dos propiedades.
- La primera propiedad es el
id
con un número de incremento que es único para cada objeto. La segunda propiedad estext
, que corresponde al texto que almacena la descripción de la foto de destino. - Almacenar todos los objetos en una matriz.
El código JavaScript que implementa esta funcionalidad:
const captions = document.querySelectorAll(".image-list figcaption p:first-child"); const myArray = []; let counter = 1; for (const caption of captions) { myArray.push({ id: counter++, text: caption.textContent }); }
Una cosa a tener en cuenta es que nuestro contador comienza a partir de 1 y no de 0. Lo hicimos intencionalmente porque esto nos ayudará a apuntar fácilmente a los elementos deseados más adelante.
Entrada del usuario
A continuación, cada vez que un usuario escribe algo en la entrada de búsqueda, hacemos lo siguiente:
- Ocultar todas las fotos.
- Capturar la consulta de búsqueda.
- Comprobar si hay elementos de la matriz (objetos) que incluyan en su propiedad
text
el valor de la consulta de búsqueda. - Mostrar los elementos que cumplen el requisito anterior.
- Imprimir su número en la pantalla. Si no hay ningún elemento imprimir 0.
El código JavaScript requerido:
const searchInput = document.querySelector('input[type="search"]'); const imageListItems = document.querySelectorAll(".image-list li"); const photosCounter = document.querySelector(".toolbar .counter span"); const dNone = "d-none"; searchInput.addEventListener("keyup", keyupHandler); function keyupHandler() { // 1 for (const item of imageListItems) { item.classList.add(dNone); } // 2 const text = this.value; // 3 const filteredArray = myArray.filter(el => el.text.includes(text)); // 4 if (filteredArray.length > 0) { for (const el of filteredArray) { document.querySelector(`.image-list li:nth-child(${el.id})`).classList.remove(dNone); } } // 5 photosCounter.textContent = filteredArray.length; }
Y la clase CSS usada aquí:
.d-none { display: none; }
Nota: Hay diferentes métodos que podemos utilizar para evitar que la función callback se ejecute cada vez que un usuario suelta una clave (evento keyup
). Aunque fuera del alcance de este tutorial, una solución eficaz podría ser usar la función debounce de Lodash.
Consejo: Si deseas realizar una búsqueda sin distinción entre mayúsculas y minúsculas, una solución simple es reemplazar la constante filteredArray
de los pasos anteriores (// 3
) por esto:
const filteredArray = myArray.filter(el => el.text.toLowerCase().includes(text.toLowerCase()));
Conclusión
¡Eso es, amigos! De hecho fue un largo viaje, sin embargo, espero que hayas aprendido algunas cosas nuevas y hayas disfrutado de la demo que construimos aquí. Juega con él y avísame si tienes alguna pregunta.
Como siempre, ¡gracias por leer!
Más proyectos de diseño CSS en Tuts+
- Diseño de cuadrícula CSSResolviendo problemas con CSS Grid y Flexbox: Diseño de interfaz mediante tarjetasIan Yates
- Diseño de cuadrícula CSSCrea una galería de imágenes con cuadrículas CSS (con un efecto de desenfoque y media queries de interacción)Ian Yates
- Selectores CSSCómo crear un componente de filtrado con solo CSSGeorge Martsoukos
- FlexboxCómo crear una página responsiva a pantalla completa con FlexboxGeorge Martsoukos
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post