Control deslizante de imagen giratorio infinito y circular CSS

Nodo de origen: 1765846

Los controles deslizantes de imágenes (también llamados carruseles) están en todas partes. Existen muchos trucos de CSS para crear el control deslizante común donde las imágenes se deslizan de izquierda a derecha (o al revés). Es el mismo trato con las muchas bibliotecas de JavaScript que hay que crean elegantes controles deslizantes con animaciones complejas. No vamos a hacer nada de eso en este post.

A través de una pequeña serie de artículos, vamos a explorar algunos controles deslizantes exclusivos de CSS elegantes y poco comunes. Si estás cansado de ver los mismos controles deslizantes clásicos, ¡entonces estás en el lugar correcto!

Serie de controles deslizantes CSS

Para este primer artículo, comenzaremos con algo que llamo el "deslizador de imagen giratorio circular":

¿Guay, verdad? ¡Diseccionemos el código!

El marcado HTML

Si seguiste mi serie de decoraciones de imagen de lujo or Cuadrícula CSS y formas personalizadas, entonces sabes que mi primera regla es trabajar con el HTML más pequeño posible. Siempre me esfuerzo por encontrar soluciones CSS antes de saturar mi código con muchas

s y otras cosas.

La misma regla se aplica aquí: nuestro código no es más que una lista de imágenes en un contenedor.

Digamos que estamos trabajando con cuatro imágenes:

¡Eso es todo! Ahora pasemos a la parte interesante del código. Pero primero, vamos a sumergirnos en esto para comprender la lógica de cómo funciona nuestro control deslizante.

¿Cómo funciona?

Aquí hay un video donde elimino overflow: hidden del CSS para que podamos entender mejor cómo se mueven las imágenes:

Es como si nuestras cuatro imágenes estuvieran colocadas en un gran círculo que gira en sentido contrario a las agujas del reloj.

Todas las imágenes tienen el mismo tamaño (indicado por S en la figura). Tenga en cuenta el círculo azul que es el círculo que se cruza con el centro de todas las imágenes y tiene un radio (R). Necesitaremos este valor más adelante para nuestra animación. R es igual a 0.707 * S. (Voy a saltarme la geometría que nos da esa ecuación).

¡Escribamos algo de CSS!

Usaremos Cuadrícula CSS para colocar todas las imágenes en la misma área una encima de la otra:

.gallery  {
  --s: 280px; /* control the size */

  display: grid;
  width: var(--s);
  aspect-ratio: 1;
  padding: calc(var(--s) / 20); /* we will see the utility of this later */
  border-radius: 50%;
}
.gallery > img {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

Nada demasiado complejo hasta ahora. La parte difícil es la animación.

Hablamos de rotar un gran círculo, pero en realidad, rotaremos cada imagen individualmente creando la ilusión de un gran círculo giratorio. Entonces, definamos una animación, m, y aplíquelo a los elementos de la imagen:

.gallery > img {
  /* same as before */
  animation: m 8s infinite linear;
  transform-origin: 50% 120.7%;
}

@keyframes m {
  100% { transform: rotate(-360deg); }
}

El truco principal se basa en esa línea resaltada. Por defecto, el CSS transform-origin la propiedad es igual a center (o 50% 50%) que hace que la imagen gire alrededor de su centro, pero no necesitamos que haga eso. Necesitamos que la imagen gire alrededor del centro de la gran círculo que contiene nuestras imágenes, de ahí el nuevo valor para transform-origin.

Como R es igual a 0.707 * S, podemos decir eso R es igual a 70.7% del tamaño de la imagen. Aquí hay una figura para ilustrar cómo obtuvimos el 120.7% valor:

Ejecutemos la animación y veamos qué sucede:

Sé que sé. El resultado dista mucho de lo que queremos, pero en realidad estamos muy cerca. Puede parecer que solo hay una imagen allí, pero no olvide que hemos apilado todas las imágenes una encima de la otra. Todos giran al mismo tiempo y solo se ve la imagen superior. Lo que necesitamos es retrasar la animación de cada imagen para evitar esta superposición.

.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 8s / 4 */
.gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 8s / 4 */
.gallery > img:nth-child(4) { animation-delay: -6s; } /* -3 * 8s / 4 */

¡Las cosas ya están mejorando!

Si ocultamos el desbordamiento en el contenedor, ya podemos ver un control deslizante, pero actualizaremos un poco la animación para que cada imagen permanezca visible durante un breve período antes de moverse.

Vamos a actualizar nuestros fotogramas clave de animación para hacer precisamente eso:

@keyframes m {
  0%, 3% { transform: rotate(0); }
  22%, 27% { transform: rotate(-90deg); }
  47%, 52% { transform: rotate(-180deg); }
  72%, 77% { transform: rotate(-270deg); }
  98%, 100% { transform: rotate(-360deg); }
}

Para cada uno 90deg (360deg/4, Donde 4 es el número de imágenes) añadiremos una pequeña pausa. Cada imagen permanecerá visible durante 5% de la duración total antes de pasar al siguiente (27%-22%, 52%-47%, etc.). voy a actualizar el animation-timing-function utilización de un cubic-bezier() función para hacer la animación un poco más elegante:

¡Ahora nuestro control deslizante es perfecto! Bueno, casi perfecto porque aún nos falta el toque final: el colorido borde circular que gira alrededor de nuestras imágenes. Podemos usar un pseudo-elemento en el .gallery envoltorio para hacerlo:

.gallery {
  padding: calc(var(--s) / 20); /* the padding is needed here */
  position: relative;
}
.gallery::after {
  content: "";
  position: absolute;
  inset: 0;
  padding: inherit; /* Inherits the same padding */
  border-radius: 50%;
  background: repeating-conic-gradient(#789048 0 30deg, #DFBA69 0 60deg);
  mask: 
    linear-gradient(#fff 0 0) content-box, 
    linear-gradient(#fff 0 0);
  mask-composite: exclude;
}
.gallery::after,
.gallery >img {
  animation: m 8s infinite cubic-bezier(.5, -0.2, .5, 1.2);
}

He creado un círculo con un gradiente cónico repetitivo para el fondo mientras usa un truco de enmascaramiento que solo muestra el área acolchada. Luego le aplico la misma animación que definimos para las imágenes.

¡Hemos terminado! Tenemos un control deslizante circular genial:

Agreguemos más imágenes

Trabajar con cuatro imágenes está bien, pero sería mejor si pudiéramos escalarlo a cualquier número de imágenes. Después de todo, este es el propósito de un control deslizante de imágenes. Deberíamos ser capaces de considerar N imágenes.

Para ello, vamos a hacer que el código sea más genérico introduciendo Sass. Primero, definimos una variable para el número de imágenes ($n) y actualizaremos cada parte donde codificamos el número de imágenes (4).

Comencemos con los retrasos:

.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 8s / 4 */
.gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 8s / 4 */
.gallery > img:nth-child(4) { animation-delay: -6s; } /* -3 * 8s / 4 */

La fórmula del retraso es (1 - $i)*duration/$n, lo que nos da el siguiente bucle Sass:

@for $i from 2 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
    animation-delay: calc(#{(1 - $i) / $n} * 8s);
  }
}

También podemos hacer que la duración sea una variable si realmente queremos. Pero pasemos a la animación:

@keyframes m {
  0%, 3% { transform: rotate(0); }
  22%, 27% { transform: rotate(-90deg); }
  47%, 52% { transform: rotate(-180deg); }
  72%, 77% { transform: rotate(-270deg); }
  98%, 100% {transform: rotate(-360deg); }
}

Vamos a simplificarlo para obtener una mejor vista del patrón:

@keyframes m {
  0% { transform: rotate(0); }
  25% { transform: rotate(-90deg); }
  50% { transform: rotate(-180deg); }
  75% { transform: rotate(-270deg); }
  100% { transform: rotate(-360deg); }
}

El paso entre cada estado es igual a 25% - cual es 100%/4 - y añadimos un -90deg ángulo, que es -360deg/4. Eso significa que podemos escribir nuestro bucle así:

@keyframes m {
  0% { transform: rotate(0); }
  @for $i from 1 to $n {
    #{($i / $n) * 100}% { transform: rotate(#{($i / $n) * -360}deg); }  
  }
  100% { transform: rotate(-360deg); }
}

Dado que cada imagen toma 5% de la animación, cambiamos esto:

#{($i / $n) * 100}%

…con este:

#{($i / $n) * 100 - 2}%, #{($i / $n) * 100 + 3}%

Cabe señalar que 5% es un valor arbitrario que elijo para este ejemplo. También podemos convertirlo en una variable para controlar cuánto tiempo debe permanecer visible cada imagen. Voy a omitir eso por simplicidad, pero como tarea, ¡puede intentar hacerlo y compartir su implementación en los comentarios!

@keyframes m {
  0%,3% { transform: rotate(0); }
  @for $i from 1 to $n {
    #{($i / $n) * 100 - 2}%, #{($i / $n) * 100 + 3}% { transform: rotate(#{($i / $n) * -360}deg); }  
  }
  98%,100% { transform: rotate(-360deg); }
}

Lo último es actualizar. transform-origin. Necesitaremos algunos trucos de geometría. Sea cual sea el número de imágenes, la configuración es siempre la misma. Tenemos nuestras imágenes (círculos pequeños) colocadas dentro de un círculo grande y necesitamos encontrar el valor del radio, R.

Probablemente no quieras una explicación aburrida de geometría, así que así es como encontramos R:

R = S / (2 * sin(180deg / N))

Si expresamos eso como un porcentaje, eso nos da:

R = 100% / (2 * sin(180deg / N)) = 50% / sin(180deg / N)

…lo que significa que transform-origin valor es igual a:

transform-origin: 50% (50% / math.sin(180deg / $n) + 50%);

¡Hemos terminado! ¡Tenemos un control deslizante que funciona con cualquier número de imágenes!

Vamos a lanzar nueve imágenes allí:

Agregue tantas imágenes como desee y actualice la $n variable con el número total de imágenes.

Terminando

Con algunos trucos usando transformaciones CSS y geometría estándar, creamos un buen control deslizante circular que no requiere mucho código. Lo bueno de este control deslizante es que no necesitamos molestarnos en duplicar las imágenes para mantener la animación infinita ya que tenemos un círculo. ¡Después de una rotación completa, volveremos a la primera imagen!

Sello de tiempo:

Mas de Trucos CSS