Saltar enlaces

Lista de avatares responsivos usando CSS moderno (Parte 1)

Una lista circular de imágenes que se superponen ligeramente entre sí es un patrón de diseño web clásico.

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.

Seguro que te preguntas qué novedad traemos a la mesa, ¿verdad? Lo he hecho innumerables veces.

tienes razón. La idea principal no es complicada, pero lo nuevo es la parte responsiva. Veremos cómo ajustar dinámicamente la superposición entre imágenes para que quepan dentro de su contenedor. ¡Vamos a hacer algunas animaciones geniales para ello!

Esta es la demostración que estamos creando. Puede cambiar el tamaño de la ventana y pasar el cursor sobre las imágenes para ver cómo se comportan. ¡Sí, los espacios entre imágenes son transparentes!

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.

Exploraremos estos temas con más profundidad en nuestro segundo artículo. ¡Ahora recreemos esta demostración!

Lista de avatares responsiva usando CSS moderno

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

Configuración inicial

Comenzamos con HTML, que es un conjunto de elementos de imagen en un contenedor principal:

<div class="container">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <!-- etc. -->
</div>

Simplemente declaramos flexbox en el contenedor para organizar las imágenes en una fila:

.container {
  display: flex;
}

Podemos convertir la imagen en un círculo usando border-radius y use un poco de presión negativa para presionarlos juntos margin:

.container img {
  border-radius: 50%;
  margin-right: -20px;
}
.container img:last-child {
  margin: 0;
}

Nada especial hasta ahora. Utilizo valores arbitrarios como márgenes para crear la superposición:

efecto de recorte

Necesitamos que mask Propiedad para cortar imágenes y crear espacios transparentes entre ellas. Hacer que los espacios sean transparentes es muy importante aquí porque hace que el componente se vea mejor, pero también es más difícil codificar porque el recorte debe tener en cuenta el elemento siguiente (o anterior) de una manera que evite que una imagen oscurezca a la otra.

mask: radial-gradient(50% 50% at calc(150% - 20px), #0000 100%, #000);

este mask Crea un círculo con las mismas dimensiones que una de las imágenes, con un radio igual a 50% en ambas direcciones: su punto central será el punto medio del siguiente elemento (calc(150% - 20px)). Si no hay superposición, el centro del siguiente elemento está en 50% (center of the actual element) + 100%. Pero debido a la superposición, la siguiente imagen está más cerca, por lo que reducimos la distancia. 20pxque es el valor utilizado para los márgenes. Esto recortará la imagen desde el lado derecho.

Si queremos el recorte a la izquierda, movemos el círculo en la otra dirección: 50% - 100% + 20px.

Arrastre el control deslizante en la siguiente demostración para visualizar cómo funciona en ambas direcciones. estoy eliminando border-radius Ilustrando la forma circular de la imagen central.

Lo aplicamos a todas las imágenes y listo. Tenga en cuenta que estoy usando varias variables CSS para controlar el tamaño de la imagen y los espacios entre imágenes.

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

  display: flex;
}
.container img {
  width: var(--s);
  border-radius: 50%;
  margin-right: -20px;
  /* Cut-out on the right side */
  mask: radial-gradient(50% 50% at calc(150% - 20px),
        #0000 calc(100% + var(--g)),#000);
}
/* Cut-out on the left side */
.container.reverse img {
  mask: radial-gradient(50% 50% at calc(-50% + 20px),
        #0000 calc(100% + var(--g)),#000);
}
.container img:last-child {
  margin: 0;
}
.container.reverse img:first-child,
.container:not(.reverse) img:last-child {
  mask: none;
}

por favor preste especial atención .reverse clase. Cambia la dirección de recorte de derecha (predeterminado) a izquierda.

Lo que tenemos ya es bueno. Funciona bien y puedes usarlo, pero podría ser más interactivo. La superposición se ve bien, pero ¿no sería mejor si pudiéramos ampliarla en pantallas más pequeñas para ayudar a ahorrar espacio, o incluso eliminarla por completo en pantallas más grandes que tienen suficiente espacio para mostrar la imagen completa?

Hagámoslo más interactivo y responsivo.

parte responsiva

Imaginemos que el tamaño total de la imagen excede .container. Esto provoca un desbordamiento, por lo que debemos asignar márgenes negativos a cada imagen para absorber ese espacio y garantizar que todas las imágenes quepan en el contenedor.

Parece que necesitamos algo de JavaScript para calcular el espacio sobrante y luego dividirlo por la cantidad de imágenes para obtener el valor del margen. Y tal vez poner esta lógica en un oyente de cambio de tamaño en caso de que el contenedor cambie de tamaño.

¡Por supuesto que estoy bromeando! Podemos resolver este problema utilizando CSS moderno que sea pequeño y fácil de mantener.

Si tuviéramos que expresar matemáticamente lo que necesitamos, la fórmula para los márgenes debería ser igual a:

margin-right: (size_of_container - N x size_of_image)/(N - 1);

…Dónde N es el número de imágenes, lo dividimos por N - 1 Porque la última imagen no necesita márgenes. Ya tenemos una variable de tamaño de imagen (--s) sabemos que el ancho del contenedor es 100%:

margin-right: (100% - N x var(--s))/(N - 1);

Lo que hay que resolver es Nel número de imágenes. Podríamos usar un número mágico estricto aquí, como 10, pero ¿qué pasa si queremos menos o más imágenes en el contenedor? Tenemos que actualizar el CSS cada vez. Queríamos una solución que pudiera adaptarse a cualquier cantidad de imágenes que le arrojáramos.

este es el nuevo lugar sibling-count() La funcionalidad resulta útil. Esta sería la mejor manera de avanzar, ya que contará automáticamente la cantidad de elementos secundarios dentro del contenedor. Entonces si hay 10 imágenes .containereste sibling-count() Son las 10.

margin-right: calc((100% - sibling-count() * var(--s))/(sibling-count() - 1));

Cambie el tamaño del contenedor en la demostración a continuación y vea cómo se comporta la imagen. de nuevo, sibling-count() Actualmente, el soporte es limitado, pero puedes comprobarlo en la última vista previa de la tecnología Chrome o Safari.

¡Bastante bien! La imagen se ajusta automáticamente para adaptarse al contenedor, pero aún podemos mejorar esto un poco. Cuando el tamaño del contenedor es lo suficientemente grande, el valor del margen calculado es positivo y el espacio entre imágenes es grande. Es posible que desees mantener este comportamiento, pero en mi caso quiero que la imagen se mantenga lo más cercana posible.

Para esto podemos establecer un límite máximo margin valor y asegúrese de que no sea mayor que 0:

margin-right: min((100% - sibling-count() * var(--s))/(sibling-count() - 1), 0px);

También podemos reutilizar la variable de brecha (--g) mantener espacio entre elementos:

margin-right: min((100% - sibling-count() * var(--s))/(sibling-count() - 1), var(--g));

Si te preguntas por qué uso min() función que define el límite máximo, lee esto para una explicación detallada. En resumen: usted establece efectivamente el máximo min() y mínimo y max().

¡La parte responsiva es perfecta ahora!

Lo que nos falta es el efecto de recorte que hicimos. mask. Para esto podemos reutilizar el mismo. margin valor dentro mask.

Hay dos imágenes en líneas separadas en los extremos opuestos de cada línea. La fila superior de imágenes está alineada a la derecha y la fila inferior de imágenes está alineada a la izquierda.

¡Ups, la imagen desapareció! Tenemos el mismo código que en la sección anterior, pero en lugar del arbitrario 20px valor, utilizamos la última fórmula.

.container img {
  --_m: min((100% - sibling-count() * var(--s))/(sibling-count() - 1), var(--g));

  margin-right: var(--_m);
  mask: radial-gradient(50% 50% at calc(150% + var(--_m)),
        #0000 calc(100% + var(--g)),#000);
}

¿Puedes adivinar cuál es el problema? Piénselo porque puede encontrarse con esta situación en otras situaciones también.

Tiene algo que ver con el porcentaje. y marginel porcentaje hace referencia al tamaño del contenedor, pero dentro de la máscara considera otra referencia, lo que significa que los valores no son iguales. Necesitamos recuperar el tamaño del contenedor de una manera diferente y en su lugar usar la unidad de consulta del contenedor.

Primero nos registramos .container Como “contenedor” de CSS:

.container {
  container-type: inline-size;
}

Entonces, podemos decir que el ancho del contenedor es 100cqi (o 100cqw) en lugar de 100%que resuelve el problema de diseño:

¡Entonces! Al cambiar el tamaño del contenedor, la posición y la máscara se ajustan perfectamente.

parte de animación

La idea de la animación es que si hay superposición entre elementos, la imagen se revela completamente al pasar el mouse, así:

¿Cómo eliminamos la superposición? Lo único que hacemos es actualizar la variable (--_m) Previamente definimos que la imagen sea cero al pasar el mouse:

.container img:hover {
  --_m: 0px;
}

esto esta sacado margin y eliminar el efecto de recorte. Es posible que realmente queramos un pequeño Hay un pequeño margen entre las imágenes, así que --_m igual a la brecha (--g) en cambio:

.container img:hover {
  --_m: var(--g);
}

¡bien! Pero podemos hacerlo mejor. Observe cómo alejar una imagen de otra hace que la imagen final desborde el contenedor. La lista inferior (la fila con el recorte a la izquierda) no es tan buena como la lista superior porque la máscara está un poco apagada al pasar el mouse.

Antes de solucionar el problema de desbordamiento, arreglemos primero la máscara.

El problema es que estoy usando margin-right El espaciado cuando el efecto de recorte está a la izquierda. Funciona bien cuando no necesitamos ninguna animación, pero como puedes ver, no es tan bueno en la última demostración. tenemos que cambiar a margin-left en lugar de en la fila inferior. En otras palabras, utilizamos margin-right cuando la incisión es en el lado derecho, y margin-left Cuando la incisión es del lado izquierdo.

.container:not(.reverse) img {
  mask: radial-gradient(50% 50% at calc(150% + var(--_m)),
        #0000 calc(100% + var(--g)), #000);
  margin-right: var(--_m);
}
.container.reverse img {
  mask: radial-gradient(50% 50% at calc(-50% - var(--_m)),
        #0000 calc(100% + var(--g)), #000);
  margin-left: var(--_m);
}
.container:not(.reverse) img:last-child,
.container.reverse img:first-child {
  mask: none;
  margin: 0;
}

Genial, ahora el recorte es mucho mejor y respeta los lados izquierdo y derecho:

Ahora arreglemos el desbordamiento. Recordando la fórmula anterior, separamos el espacio sobrante N - 1 ¿elemento?

(size_of_container - N x size_of_image)/(N - 1)

Ahora necesitamos excluir un elemento más de la ecuación, lo que significa que N y N - 1 y reemplazar N - 1 y N - 2:

(size_of_container - (N - 1) x size_of_image)/(N - 2)

Sin embargo, los elementos adicionales excluidos siguen ocupando espacio dentro del contenedor. Necesitamos tener en cuenta su tamaño y restarlo del tamaño del contenedor:

((size_of_container - (size_of_image + gap)) - (N - 1) x size_of_image)/(N - 2)

Estoy considerando talla más liquidación porque margin Esto es igual al espacio establecido en la imagen al pasar el mouse, que es el espacio adicional que debemos eliminar.

Simplificémoslo un poco:

(size_of_container - gap -  N x size_of_image)/(N - 2)

Sabemos cómo convertirlo a CSS, pero ¿dónde lo aplicamos?

Cuando se desplaza el cursor sobre una imagen, debería aplicarse a todas las imágenes (excepto a la imagen suspendida). Esta es una gran oportunidad para escribir un buen selector. :has() y :not()!

/* Select images that are not hovered when the container contains a hovered image */
.container:has(:hover) img:not(:hover) {
  /**/
}

Sustituimos la fórmula en ella:

.container:has(:hover) img:not(:hover) {
  --_m: min((100cqw - var(--g) - sibling-count()*var(--s))/(sibling-count() - 2), var(--g));
}

Mira esto: ¡no más desbordamiento al pasar el cursor en ambas direcciones! Lo que nos falta ahora es un espaciado de transición suave en lugar de una animación real para colocar las cosas en su lugar. Todo lo que necesitamos es agregar un poco transition En --_m Cambiable:

transition: --_m .3s linear;

Sin embargo, si lo hacemos transition No sucederá. Esto se debe a que CSS no reconoce el valor calculado como el valor correcto. unidad de longitud CSS. Para hacer esto necesitamos registrarnos oficialmente. --_m Como atributo personalizado, utilice @property en las reglas:

@property --_m {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px
}

Empecemos:

Genial, ¿verdad? El suave cambio de máscara y posición es bastante satisfactorio. Todavía tenemos que arreglar un pequeño caso extremo. El último elemento de la lista superior y el primer elemento de la lista inferior no tienen márgenes y siempre son completamente visibles, por lo que debemos excluirlos del efecto.

Al pasar el cursor sobre ellos no pasa nada, por lo que podemos ajustar el selector anterior así:

.container:not(.reverse):has(:not(:last-child):hover) img:not(:hover),
.container.reverse:has(:not(:first-child):hover) img:not(:hover) {
  --_m: min((100cqw - var(--g) - sibling-count()*var(--s))/(sibling-count() - 2),var(--g));
}

En lugar de simplemente verificar si el contenedor tiene un elemento suspendido, limitamos la selección a elementos que no tienen un elemento suspendido. :last-child para la primera lista en lugar de :first-child para la segunda lista. ¡Otro selector genial que usa CSS moderno!

Aquí está la demostración final después de todos los ajustes:

en conclusión

Espero que hayas disfrutado de esta pequeña exploración de algunas características modernas de CSS. Recreamos un componente clásico, pero el objetivo real es aprender algunos trucos de CSS y confiar en nuevas características que definitivamente necesitarás en otras situaciones.

En el próximo artículo agregaremos más complejidad y cubriremos CSS más moderno para un efecto uniforme. más ¡Satisfecho con el patrón! Manténganse al tanto.

Lista de avatares responsiva usando CSS moderno

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