Saltar enlaces

Lista de avatares responsivos usando CSS moderno (Parte 2)

¿Estás listo para la segunda parte? Si todavía lo recuerdas, último tiempo Hicimos una lista de respuestas para imágenes de avatar superpuestas, con recortes entre ellas.

Dos filas de imágenes de avatar circulares. Estas imágenes se superponen entre sí. La primera fila tiene ocho imágenes; la segunda fila tiene seis imágenes.

Todavía estamos creando una lista de avatares responsiva, pero esta vez será una lista circular.

Muestra un ejemplo de dos imágenes de avatar circulares dispuestas en círculo. El primer ejemplo tiene ocho imágenes. El segundo ejemplo tiene seis imágenes.

Este diseño es menos común que las listas horizontales, pero sigue siendo un gran ejercicio para explorar nuevas técnicas de CSS.

Comencemos con una demostración. Puede cambiar su tamaño y ver cómo se comportan las imágenes, y puede pasar el cursor sobre ellas para obtener un efecto de visualización atractivo.

La siguiente demostración está actualmente limitada a Chrome y Edge, pero también funcionará en otros navegadores. sibling-index() y sibling-count() Las funciones obtienen un soporte más amplio. Puede realizar un seguimiento del soporte de Firefox en: Boleto #1953973 y el estado de WebKit Número 471.

Nos basaremos en la misma estructura HTML y conceptos básicos de CSS que los ejemplos que cubrimos en Parte 1: lista de imágenes dentro del contenedor mask-Recorte de papel. Esta vez, sin embargo, la postura será diferente.

Lista de avatares responsiva usando CSS moderno

  1. lista horizontal
  2. lista de ciclos (¡Usted está aquí!)

Coloca la imagen alrededor del círculo.

Hay varias técnicas Se utiliza para colocar imágenes alrededor de un círculo.. Comenzaré con uno de mis favoritos, que es menos conocido pero usa un código simple que se basa en CSS. offset propiedad.

.container {
  display: grid;
}
.container img {
  grid-area: 1/1;
  offset: circle(180px) calc(100%*sibling-index()/sibling-count()) 0deg;
}

El código no parece muy intuitivo, pero la lógica es bastante simple. este offset La propiedad es una abreviatura, así que escribámosla de la forma normal para ver cómo se descompone:

offset-path: circle(180px);
offset-distance: calc(100%*sibling-index()/sibling-count());
offset-rotate: 0deg;

Definimos un camino como un círculo con radio. 180px. Todas las imágenes “seguirán” este camino, pero inicialmente se superpondrán entre sí. Necesitamos ajustar su distancia para cambiar su posición a lo largo del camino (es decir, el círculo). eso esta ahí offset-distance entra en juego, lo combinamos con sibling-index() y sibling-count() función para crear código que funcione para cualquier número de elementos, en lugar de utilizar números exactos.

Para seis elementos, los valores son los siguientes:

100% x 1/6 = 16.67%
100% x 2/6 = 33.33%
100% x 3/6 = 50%
100% x 4/6 = 66,67%
100% x 5/6 = 83.33%
100% x 6/6 = 100%

Esto colocará los elementos uniformemente alrededor del círculo. Para hacer esto, sumamos un igual a 0deg usar offset-rotate Mantenga los elementos rectos para que no giren mientras siguen una trayectoria circular. A partir de ahí lo único que nos queda es actualizar el radio del círculo con el valor que queramos.

Este es mi método preferido, pero hay un segundo método que usa transform Propiedades que combinan dos rotaciones y traslaciones:

.container {
  display: grid;
}
.container img {
  grid-area: 1/1;
  --_i: calc(1turn*sibling-index()/sibling-count());
  transform: rotate(calc(-1*var(--_i))) translate(180px) rotate(var(--_i));
}

La traducción contiene el valor del radio del círculo, la rotación depende de sibling-* La funcionalidad es la misma que la nuestra. offset-distance.

Aunque prefiero el primer método, confiaré en el segundo porque me permite reutilizar el ángulo de rotación en más lugares.

parte de respuesta

De manera similar a la lista de respuesta horizontal del artículo anterior, confiaré en la celda de consulta del contenedor para definir el radio del círculo y hacer que el componente responda.

Diagrama de ocho imágenes de avatar circulares dispuestas alrededor de un círculo. La línea discontinua roja indica el tamaño y el radio del gran círculo.
.container {
  --s: 120px; /* image size */

  aspect-ratio: 1;
  container-type: inline-size;
}
.container img {
  width: var(--s);
  --_r: calc(50cqw - var(--s)/2);
  --_i: calc(1turn*sibling-index()/sibling-count());
  transform: rotate(calc(-1*var(--_i))) translate(var(--_r)) rotate(var(--_i));
}

Cambie el tamaño del contenedor en la demostración a continuación y vea cómo se comporta la imagen:

Responde, pero cuando el contenedor se hace más grande, las imágenes se dispersan demasiado, lo cual no me gusta. Manténgalos lo más cerca posible. En otras palabras, consideramos el círculo más pequeño que contiene todas las imágenes sin superposición.

Recuerde lo que hicimos en la primera parte: agregamos un margen máximo a los márgenes por razones similares. Haremos lo mismo aquí:

--_r: min(50cqw - var(--s)/2, R);

Sé que no quieres tomar una clase aburrida de geometría, así que la saltaré y te daré el valor. R:

S/(2 x sin(.5turn/N))

Escrito en CSS:

--_r: min(50cqw - var(--s)/2,var(--s)/(2*sin(.5turn/sibling-count())));

Ahora, cuando agrandes el contenedor, las imágenes permanecerán cerca unas de otras, lo cual es perfecto:

Introduzcamos otra variable para representar la brecha entre imágenes (--g) y actualice ligeramente la fórmula para mantener pequeños espacios entre imágenes.

.container {
  --s: 120px; /* image size */
  --g: 10px;  /* the gap */

  aspect-ratio: 1;
  container-type: inline-size;
}
.container img {
  width: var(--s);
  --_r: min(50cqw - var(--s)/2,(var(--s) + var(--g))/(2*sin(.5turn/sibling-count())));
  --_i: calc(1turn*sibling-index()/sibling-count());
  transform: rotate(calc(-1*var(--_i))) translate(var(--_r)) rotate(var(--_i));
}

efecto de recorte

Para esta parte usaremos la misma máscara que usamos en el artículo anterior:

mask: radial-gradient(50% 50% at X Y, #0000 calc(100% + var(--g)), #000);

Para listas horizontales, el valor X y Y Muy sencillo. no tenemos que definir Y porque su valor predeterminado hace el trabajo, y X el valor es 150% + M o -50% - My M Es el margen que controla la superposición. Míralo desde otro ángulo, X y Y son las coordenadas del punto central de la imagen anterior o siguiente de la lista.

Este sigue siendo el caso esta vez, pero el cálculo del valor es más complicado:

Diagrama de ocho imágenes de avatar circulares dispuestas alrededor de un círculo. Los dos segmentos de línea están marcados como segmento de línea roja A y segmento de línea verde B respectivamente. El primer segmento apunta a la imagen actual representada por i. El segundo segmento apunta a la siguiente imagen representada por i más 1.

La idea es comenzar desde el centro de la imagen actual (50% 50%) y pasar al centro de la siguiente imagen (X y Y). Primero seguiré el segmento A hasta el centro del círculo máximo, luego el segmento B hasta el centro de la siguiente imagen.

Aquí está la fórmula:

X = 50% - Ax + Bx
Y = 50% - Ay + By

Ax y Ay es la proyección del segmento de línea A en el eje X y el eje Y. Podemos usar funciones trigonométricas para obtener el valor.

Ax = r x sin(i);
Ay = r x cos(i);

este r Representa el radio de un círculo definido por una variable CSS --_ry i Representa el ángulo de rotación definido por las variables CSS. --_i.

La misma lógica que la sección B:

Bx = r x sin(j);
By = r x cos(j);

este j Similar a ipero para Próximo imagen en la secuencia, esto significa que aumentamos el índice en 1. Esto nos da los siguientes cálculos CSS para cada variable:

--_i: calc(1turn*sibling-index()/sibling-count());
--_j: calc(1turn*(sibling-index() + 1)/sibling-count());

Y el código final con máscara:

.container {
  --s: 120px; /* image size */
  --g: 14px;  /* the gap */

  aspect-ratio: 1;
  container-type: inline-size;
}
.container img {
  width: var(--s);
  --_r: min(50cqw - var(--s)/2,(var(--s) + var(--g))/(2*sin(.5turn/sibling-count())));
  --_i: calc(1turn*sibling-index()/sibling-count());
  --_j: calc(1turn*(sibling-index() + 1)/sibling-count());
  transform: rotate(calc(-1*var(--_i))) translate(var(--_r)) rotate(var(--_i));
  mask: radial-gradient(50% 50% at
    calc(50% + var(--_r)*(cos(var(--_j)) - cos(var(--_i))))
    calc(50% + var(--_r)*(sin(var(--_i)) - sin(var(--_j)))),
      #0000 calc(100% + var(--g)), #000);
}

Genial, ¿verdad? Es posible que observe dos implementaciones diferentes de recorte. La fórmula que utilicé antes tomó en cuenta la siguiente imagen, pero si consideramos anterior La imagen está invertida, con el recorte mirando en la otra dirección. Entonces, en lugar de incrementar el índice, lo disminuimos y lo asignamos a .reverse Esta clase se puede utilizar cuando queremos que el recorte se mueva en la dirección opuesta:

.container img {
  --_j: calc(1turn*(sibling-index() + 1)/sibling-count());
}
.container.reverse img {
  --_j: calc(1turn*(sibling-index() - 1)/sibling-count());
}

parte de animación

De manera similar a lo que hicimos en el artículo anterior, el objetivo de esta animación es eliminar la superposición cuando se pasa el cursor sobre la imagen para revelarla por completo. En una lista horizontal, simplemente configuramos su margin propiedad a 0ajustamos los márgenes de otras imágenes para evitar el desbordamiento.

Esta vez la lógica es diferente. Rotaremos todas las imágenes excepto la imagen flotante hasta que la imagen flotante sea completamente visible. Por supuesto, el sentido de giro dependerá de la dirección del corte.

Ocho imágenes de avatar están dispuestas en círculo. Las flechas apuntan a lo mismo y muestran lo que sucede cuando pasas el cursor sobre el avatar en la parte superior del círculo.

Para rotar la imagen necesitamos actualizar. --_i Variable utilizada como argumento de la función de rotación. Comencemos con un valor de rotación arbitrario, digamos 20deg.

.container img {
  --_i: calc(1turn*sibling-index()/sibling-count());
}
.container:has(:hover) img {
  --_i: calc(1turn*sibling-index()/sibling-count() + 20deg);
}
.container.reverse:has(:hover) img {
  --_i: calc(1turn*sibling-index()/sibling-count() - 20deg);
}

Todas las imágenes ahora giran cuando se desplazan sobre ellas. 20deg. Pruébelo en la demostración a continuación.

Bueno, la imagen gira, pero la máscara no sigue la rotación. No olvides que la máscara tiene en cuenta la posición definida por la imagen siguiente o anterior. --_j y la imagen siguiente/anterior está rotando, por lo que también debemos actualizar --_j Variable cuando se produce el desplazamiento.

.container img {
  --_i: calc(1turn*sibling-index()/sibling-count());
  --_j: calc(1turn*(sibling-index() + 1)/sibling-count());
}
.container.reverse img {
  --_j: calc(1turn*(sibling-index() - 1)/sibling-count());
}
.container:has(:hover) img {
  --_i: calc(1turn*sibling-index()/sibling-count() + 20deg);
  --_j: calc(1turn*(sibling-index() + 1)/sibling-count() + 20deg);
}
.container.reverse:has(:hover) img {
  --_i: calc(1turn*sibling-index()/sibling-count() - 20deg);
  --_j: calc(1turn*(sibling-index() - 1)/sibling-count() - 20deg);
}

Eso es mucho código redundante. Optimicémoslo un poco definiendo variables adicionales:

.container img {
  --_a: 20deg;

  --_i: calc(1turn*sibling-index()/sibling-count() + var(--_ii, 0deg));
  --_j: calc(1turn*(sibling-index() + 1)/sibling-count() + var(--_jj, 0deg));
}
.container.reverse img {
  --_i: calc(1turn*sibling-index()/sibling-count() - var(--_ii, 0deg));
  --_j: calc(1turn*(sibling-index() - 1)/sibling-count() - var(--_jj, 0deg));
}
.container:has(:hover) img {
  --_ii: var(--_a);
  --_jj: var(--_a);
}

ángulo actual (--_a) está definido en un solo lugar, considero dos variables intermedias para agregar un desplazamiento --_i y --_j variable.

Ahora la rotación de todas las imágenes es perfecta. Deshabilitemos la rotación de la imagen al pasar el mouse:

.container img:hover {
  --_ii: 0deg;
  --_jj: 0deg;
}

¡Ups, la máscara se ha vuelto a quitar! ¿Ves esta pregunta?

Queremos evitar que la imagen flotante gire y al mismo tiempo permitir que el resto de las imágenes giren. por lo tanto, --_j La variable para la imagen flotante debe actualizarse ya que está vinculada a la imagen anterior o siguiente. Entonces deberíamos eliminar --_jj: 0deg y mantener sólo --_ii: 0deg.

.container img:hover {
  --_ii: 0deg;
}

Esto es mejor. Arreglamos el efecto de recorte en las imágenes al pasar el mouse, pero el efecto general aún no es perfecto. No olvidemos que la imagen suspendida es la imagen anterior o siguiente de otra imagen y, como no gira, la otra imagen --_j Las variables deben permanecer sin cambios.

Para la primera lista, las variables de la imagen anterior deben permanecer sin cambios. Para la segunda lista es la variable de la siguiente imagen:

/* select previous element of hovered */
.container:not(.reverse) img:has(+ :hover),
/* select next element of hovered */
.container.reverse img:hover + * {
  --_jj: 0deg;
}

Si te preguntas cómo supe hacer esto, bueno, probé ambos métodos y me decidí por el que funcionó. O el código anterior o este:

.container:not(.reverse) img:hover + *,
.container.reverse img:has(+ :hover) {
  --_jj: 0deg;
}

¡Estamos cada vez más cerca! Todas las imágenes excepto una en cada lista se comportan normalmente. Intente colocarlos sobre todos para encontrar al culpable.

¿Puedes descubrir lo que nos estamos perdiendo? Piénselo.

Nuestra lista es circular, pero el código HTML no, por lo que aunque la primera y la última imagen están visualmente colocadas una al lado de la otra, en el código no lo están. no podemos usar selector de hermano vecino (+). También necesitamos dos selectores para cubrir estos casos extremos:

.container.reverse:has(:last-child:hover) img:first-child,
.container:not(.reverse):has(:first-child:hover) img:last-child {
  --_jj: 0deg;
}

¡acuñar! Hemos solucionado todos los problemas y ahora tenemos un excelente efecto de desplazamiento, pero aún no es perfecto. Ahora debemos ser precisos y no utilizar valores arbitrarios para la rotación. Tenemos que encontrar el mínimo que elimine la superposición manteniendo las imágenes lo más cercanas posible.

Muestra los espacios entre dos imágenes en tres puntos diferentes. Los puntos primero y tercero están demasiado cerca y demasiado lejos respectivamente. El punto medio es perfecto con mucho espacio entre las imágenes.

Podemos obtener este valor mediante algunas funciones trigonométricas. Me saltaré la lección de geometría nuevamente (¡ya tenemos suficientes dolores de cabeza!) y les daré el valor:

--_a: calc(2*asin((var(--s) + var(--g))/(2*var(--_r))) - 1turn/sibling-count());

¡Ahora podemos decir que todo es perfecto!

en conclusión

Esto es un poco difícil, ¿verdad? Si estás un poco confundido por todas las fórmulas complicadas, no te preocupes. Son muy específicos de este ejemplo, por lo que no importa si los has olvidado. El objetivo es explorar algunas características modernas y algunos trucos de CSS como offset, mask, sibling-* funciones, unidades de consulta de contenedores, min()/max()¡Y más!

Lista de avatares responsiva usando CSS moderno

  1. lista horizontal
  2. lista de ciclos (¡Usted está aquí!)
Home
Account
Cart
Search
¡Hola! ¡Pregúntame lo que quieras!
Explore
Drag