Saltar enlaces

Cuadrícula hexagonal responsiva usando CSS moderno

Hace cinco años publiqué un artículo sobre cómo crear Cuadrícula responsiva con forma hexagonal.. Esta es la única técnica que no requiere consultas de medios ni JavaScript. Funciona con cualquier cantidad de proyectos, lo que le permite controlar fácilmente el tamaño y los espacios mediante variables CSS.

estoy usando float, inline-blockambiente font-size igual 0etc. En 2026, esto puede parecer un poco anticuado y anticuado. Este no es el caso, ya que este enfoque funciona bien y cuenta con un buen soporte, pero ¿podemos hacerlo mejor utilizando funciones modernas? ¡Muchas cosas han cambiado en cinco años y podemos mejorar la implementación anterior y hacerla menos engorrosa!

Chrome solo es compatible porque esta tecnología utiliza funciones lanzadas recientemente, incluidas corner-shape, sibling-index()y División de unidades.

El código CSS es más corto y contiene menos contenido. numero magico Más que la última vez que abordé este tema. También encontrará algunos cálculos complejos que analizaremos juntos.

Antes de sumergirse en esta nueva demostración, recomiendo leer mi ultimo articulo Primero. Esto no es obligatorio, pero le permite comparar los dos enfoques y ver hasta qué punto (y qué tan rápido) ha evolucionado CSS en los últimos cinco años con la introducción de nuevas características que hacen que cosas difíciles como esta sean más fáciles.

hexágono

comencemos con hexágonoque es el elemento principal de nuestra grilla. Antes tenía que confiar en clip-path: polygon() Créelo:

.hexagon {
  --s: 100px;
  width: var(--s);
  height: calc(var(--s) * 1.1547);
  clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);
}

Pero ahora podemos confiar en nuevas corner-shape y border-radius propiedad:

.hexagon {
  width: 100px;
  aspect-ratio: cos(30deg);
  border-radius: 50% / 25%;
  corner-shape: bevel;
}

Es más fácil de lo que estábamos acostumbrados a biselar elementos y, como beneficio adicional, ¡podemos agregar un borde a la forma sin ninguna solución alternativa!

este corner-shape La propiedad es la primera característica moderna de la que dependemos. hace dibujo formas CSS Mucho más fácil que los métodos tradicionales, como el uso clip-path. Aún puedes seguir usando clip-path método, por supuesto, para un mejor soporte (si no necesita un borde en el elemento), pero aquí hay un Una implementación más moderna:

.hexagon {
  width: 100px;
  aspect-ratio: cos(30deg);
  clip-path: polygon(-50% 50%,50% 100%,150% 50%,50% 0);
}

Hay menos puntos dentro del polígono, reemplazamos el número mágico 1.1547 con un aspect-ratio declaración. No dedicaré más tiempo al código de formas, pero si desea una explicación detallada con más ejemplos, aquí hay dos artículos que escribí:

cuadrícula responsiva

Ahora que tenemos la forma, creemos la cuadrícula. Se llama “cuadrícula” pero usaré la configuración de flexbox:

<div class="container">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <!-- etc. -->
</div>
.container {
  --s: 120px; /* size  */
  --g: 10px; /* gap */
  
  display: flex;
  gap: var(--g);
  flex-wrap: wrap;
}
.container > * {
  width: var(--s);
  aspect-ratio: cos(30deg);
  border-radius: 50% / 25%;
  corner-shape: bevel;
}

Nada especial hasta ahora. A partir de ahí, agregamos un margen inferior a todos los elementos para crear superposición entre las filas:

.container > * {
  margin-bottom: calc(var(--s)/(-4*cos(30deg)));
}

El último paso es agregar un margen izquierdo a los primeros elementos de las filas pares (es decir, segundo, cuarto, sexto, etc.). Este margen creará un desplazamiento entre filas para lograr una cuadrícula perfecta.

Dicho de esta manera, parece simple, pero esta es la parte más complicada y requiere cálculos complejos. La cuadrícula responde, por lo que el “primer” elemento que buscamos puede ser cualquier elemento, según el tamaño del contenedor, el tamaño del elemento, los espacios, etc.

Empecemos con una imagen:

Dos rejillas hexagonales dispuestas una al lado de la otra. Las variables N y M se encuentran entre ellas, donde la variable N representa filas impares y M representa filas pares.

Dependiendo de la capacidad de respuesta, nuestra cuadrícula puede tener dos lados. Podemos tener la misma cantidad de elementos en todas las filas (cuadrícula 1 en la imagen de arriba), o podemos tener una diferencia de elemento entre dos filas consecutivas (cuadrícula 2). este N y M La variable representa el número de elementos en la fila. En la cuadrícula 1 tenemos N = Men la cuadrícula 2 tenemos M = N - 1.

En la Cuadrícula 1, los elementos del margen izquierdo son 6, 16, 26, etc., y en la Cuadrícula 2, los elementos del margen izquierdo son 7, 18, 29, etc. Intentemos identificar la lógica detrás de estos números.

El primer elemento en ambas cuadrículas (6 o 7) es el primer elemento en la segunda fila, por lo que es el elemento N + 1. El segundo elemento (16 o 18) es el primer elemento de la tercera fila, por lo que es el elemento N + M + N + 1. El tercer elemento (26 o 29) es el elemento N + M + N + M + N + 1. Si miras de cerca, puedes ver un patrón que podemos expresar usando la siguiente fórmula:

N*i + M*(i - 1) + 1

…Dónde i es un número entero positivo (excluyendo cero). El elemento que buscamos lo podemos encontrar utilizando el siguiente pseudocódigo:

for(i = 0; i< ?? ;i++) {
  index = N*i + M*(i - 1) + 1
  Add margin to items(index)  
}

Sin embargo, no tenemos bucles en CSS, por lo que tenemos que hacer algo diferente. Podemos obtener el índice de cada elemento usando el nuevo método. sibling-index() Función. La lógica es probar si el índice coincide con la fórmula anterior.

En lugar de escribir:

index = N*i + M*(i - 1) + 1

…expresemos i Índice de uso:

i = (index - 1 + M)/(N + M)

sabemos i es un número entero positivo (excluyendo cero), por lo que para cada elemento obtenemos su índice y probamos si (index - 1 + M)/(N + M) es un número entero positivo. Antes de eso, primero calculemos la cantidad de elementos. N y M.

Contar el número de elementos en cada fila es lo mismo que contar cuántos elementos puede contener la fila.

N = round(down,container_size / item_size);

Divide el tamaño del contenedor por el tamaño del artículo para obtener un número. si nosotros round()`Redondeando esto al número entero más cercano, obtenemos el número de elementos por fila. Pero hay brechas entre proyectos, por lo que debemos tenerlo en cuenta en la fórmula:

N = round(down, (container_size + gap)/ (item_size + gap));

hacemos lo mismo Mpero esta vez también debemos considerar el margen izquierdo aplicado al primer elemento de la fila:

M = round(down, (container_size + gap - margin_left)/ (item_size + gap));

Echemos un vistazo más de cerca y determinemos el valor de este margen en la imagen a continuación:

Especifica el ancho de una única forma hexagonal y el margen izquierdo entre filas, que es la mitad del ancho del elemento.

Es igual a la mitad del tamaño del artículo, más la mitad del espacio:

M = round(down, (container_size + gap - (item_size + gap)/2)/(item_size + gap));

M = round(down, (container_size - (item_size - gap)/2)/(item_size + gap));

Las dimensiones y espacios libres del artículo utilizan las siguientes definiciones. --s y --g variable, pero ¿qué pasa con el tamaño del contenedor? Podemos confiar en la unidad de consulta del contenedor y usar 100cqw.

Escribamos lo que tenemos hasta ahora usando CSS:

.container {
  --s: 120px;  /* size  */
  --g: 10px;   /* gap */
  
  container-type: inline-size; /* we make it a container to use 100cqw */
}
.container > * {
  --_n: round(down,(100cqw + var(--g))/(var(--s) + var(--g)));
  --_m: round(down,(100cqw - (var(--s) - var(--g))/2)/(var(--s) + var(--g))); 
  --_i: calc((sibling-index() - 1 + var(--_m))/(var(--_n) + var(--_m)));
  
  margin-left: ???; /* We're getting there! */
}

podemos usar mod(var(--_i),1) Pruebe si --_i es un número entero. Si es un número entero, el resultado es igual a 0. En caso contrario, es igual a un valor entre 0 y 1.

Podemos introducir otra variable y usar la nueva if() ¡Función!

.container {
  --s: 120px;  /* size  */
  --g: 10px;   /* gap */
  
  container-type: inline-size; /* we make it a container to use 100cqw */
}
.container > * {
  --_n: round(down,(100cqw + var(--g))/(var(--s) + var(--g)));
  --_m: round(down,(100cqw - (var(--s) - var(--g))/2)/(var(--s) + var(--g))); 
  --_i: calc((sibling-index() - 1 + var(--_m))/(var(--_n) + var(--_m)));
  --_c: mod(var(--_i),1);
  margin-left: if(style(--_c: 0) calc((var(--s) + var(--g))/2) else 0;);
}

¡Entonces!

Tenga en cuenta que necesita registrar esta variable --_c Uso variable @property para poder comparar (estoy en “Cómo usarlo correctamente if()en CSS).

Este es un buen caso de uso. if()pero podemos hacerlo de otra manera:

--_c: round(down, 1 - mod(var(--_i), 1));

este mod() La función nos da un valor entre 0 y 1, donde 0 es el valor que queremos. -1*mod() Danos un valor entre -1 y 0. 1 - mod() Danos un valor entre 0 y 1, pero esta vez es 1 el que necesitamos. aplicamos round() Se hace un cálculo y el resultado será 0 o 1. --_c La variable ahora es una variable booleana y podemos usarla directamente en nuestros cálculos.

margin-left: calc(var(--_c) * (var(--s) + var(--g))/2);

si --_c Igual a 1, obtenemos margen. De lo contrario, el margen es igual a 0. Esta vez no es necesario registrar la variable usando @property. Personalmente prefiero este enfoque ya que requiere menos código, pero if() El método también es interesante.

¿Debo recordar todas estas fórmulas? ¡Eso es demasiado!

No, no lo haces. Intenté proporcionar una explicación detallada detrás de las matemáticas, pero no es necesario entenderlas para usar la cuadrícula. Todo lo que tienes que hacer es actualizar las variables que controlan las dimensiones y los espacios. No es necesario tocar la parte que marca el margen izquierdo. ¡Incluso exploraremos cómo funciona la misma estructura de código con más formas!

Más ejemplos

Un caso de uso común son los hexágonos, pero ¿qué pasa con otras formas? Por ejemplo, podemos considerar un rombo, y para ello sólo necesitamos ajustar el código que controla la forma.

de este modo:

.container > * {
  aspect-ratio: cos(30deg);
  border-radius: 50% / 25%;
  corner-shape: bevel;
  margin-bottom: calc(var(--s)/(-4*cos(30deg)));
}

…a esto:

.container > * {
  aspect-ratio: 1;
  border-radius: 50%;
  corner-shape: bevel;
  margin-bottom: calc(var(--s)/-2);
}

Responsive Diamond Grid: ¡sin esfuerzo! Intentémoslo con el octágono:

.container > * {
  aspect-ratio: 1;
  border-radius: calc(100%/(2 + sqrt(2)));
  corner-shape: bevel;
  margin-bottom: calc(var(--s)/(-1*(2 + sqrt(2))));
}

¡casi! Para el octágono, necesitamos ajustar gap Porque necesitamos más espacio horizontal entre elementos:

.container {
  --g: calc(10px + var(--s)/(sqrt(2) + 1));
  gap: 10px var(--g);
}

variable --g incluir parte del tamaño var(--s)/(sqrt(2) + 1) y se utilizan como espacios entre filas, mientras que los espacios entre columnas permanecen sin cambios (10px).

A partir de ahí también podemos obtener otro tipo de malla hexagonal:

¿Por qué no utilizar también una cuadrícula circular? Aquí vamos:

Como puede ver, no hemos tocado los complejos cálculos para establecer el margen izquierdo en ninguno de estos ejemplos. Todo lo que tenemos que hacer es y border-radius y aspect-ratio Propiedades para controlar la forma y ajustar el margen inferior para corregir la superposición. En algunos casos necesitamos ajustar el espacio horizontal.

en conclusión

Terminaré este artículo con otra demostración como pequeña tarea para ti:

Esta vez, el cambio se aplica a las filas impares en lugar de a las pares. Te dejaré analizar el código como un pequeño ejercicio. Tratando de identificar los cambios que hice y cuál era la lógica detrás de ellos (pista: Intente rehacer los pasos de cálculo con esta nueva configuración. )

Home
Account
Cart
Search
¡Hola! ¡Pregúntame lo que quieras!
Explore
Drag