CSS Infinite och Circular Rotating Image Slider

Källnod: 1765846

Bildreglage (även kallade karuseller) finns överallt. Det finns många CSS-knep för att skapa det vanliga skjutreglaget där bilderna glider från vänster till höger (eller tvärtom). Det är samma affär med de många JavaScript-biblioteken där ute som skapar snygga reglage med komplexa animationer. Vi kommer inte att göra något av det i det här inlägget.

Genom en liten serie artiklar kommer vi att utforska några tjusiga och ovanliga skjutreglage för endast CSS. Om du är trött på att se samma gamla klassiska reglage, då är du på rätt plats!

CSS Sliders-serien

För den här första artikeln kommer vi att börja med något jag kallar det "cirkulära roterande bildreglaget":

Coolt eller hur? låt oss dissekera koden!

HTML-uppmärkningen

Om du följde min serie av snygga bilddekorationer or CSS-rutnät och anpassade former, då vet du att min första regel är att arbeta med minsta möjliga HTML. Jag försöker alltid hårt att hitta CSS-lösningar innan jag belamrar min kod med mycket

s och annat.

Samma regel gäller här - vår kod är inget annat än en lista över bilder i en behållare.

Låt oss säga att vi arbetar med fyra bilder:

Det är allt! Låt oss nu gå till den intressanta delen av koden. Men först ska vi dyka in i detta för att förstå logiken i hur vår reglage fungerar.

Hur fungerar det?

Här är en video där jag tar bort overflow: hidden från CSS så att vi bättre kan förstå hur bilderna rör sig:

Det är som att våra fyra bilder är placerade på en stor cirkel som roterar moturs.

Alla bilder har samma storlek (betecknas med S i figuren). Notera den blå cirkeln som är den cirkel som skär med mitten av alla bilder och har en radie (R). Vi kommer att behöva detta värde senare för vår animering. R är lika med 0.707 * S. (Jag kommer att hoppa över geometrin som ger oss den ekvationen.)

Låt oss skriva lite CSS!

Vi kommer att använda CSS-rutnät för att placera alla bilder i samma område ovanför varandra:

.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;
}

Inget för komplicerat än så länge. Det knepiga är animationen.

Vi pratade om att rotera en stor cirkel, men i verkligheten kommer vi att rotera varje bild individuellt och skapa illusionen av en stor roterande cirkel. Så, låt oss definiera en animation, m, och tillämpa den på bildelementen:

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

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

Huvudtricket bygger på den markerade raden. Som standard är CSS transform-origin egendom är lika med center (eller 50% 50%) vilket gör att bilden roterar runt mitten, men vi behöver den inte för att göra det. Vi behöver bilden för att rotera runt mitten av stor cirkel som innehåller våra bilder därav det nya värdet för transform-origin.

Eftersom R är lika med 0.707 * S, Vi kan säga så R är lika med 70.7% av bildstorleken. Här är en figur för att illustrera hur vi fick det 120.7% värde:

Låt oss köra animeringen och se vad som händer:

Jag vet jag vet. Resultatet är långt ifrån vad vi vill ha, men i verkligheten är vi väldigt nära. Det kan se ut som att det bara finns en bild där, men glöm inte att vi har staplat alla bilder ovanpå varandra. Alla roterar samtidigt och endast den översta bilden är synlig. Vad vi behöver är att fördröja animeringen av varje bild för att undvika denna överlappning.

.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 */

Saker och ting blir redan bättre!

Om vi ​​döljer överflödet på behållaren kan vi redan se ett skjutreglage, men vi kommer att uppdatera animationen lite så att varje bild förblir synlig en kort period innan den flyttas.

Vi kommer att uppdatera våra animerade nyckelrutor för att göra just det:

@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); }
}

För varje 90deg (360deg/4Där 4 är antalet bilder) lägger vi till en liten paus. Varje bild förblir synlig för 5% av den totala varaktigheten innan vi glider till nästa (27%-22%, 52%-47%, etc.). Jag ska uppdatera animation-timing-function med användning av en cubic-bezier() funktion för att göra animationen lite snyggare:

Nu är vår slider perfekt! Jo, nästan perfekt eftersom vi fortfarande saknar den sista touchen: den färgglada cirkulära kanten som roterar runt våra bilder. Vi kan använda ett pseudoelement på .gallery omslag för att göra det:

.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);
}

Jag har skapat en cirkel med en upprepande konisk gradient för bakgrunden när du använder en maskeringstrick som bara visar det vadderade området. Sedan använder jag samma animation som vi definierade för bilderna.

Vi är klara! Vi har ett coolt cirkulärt reglage:

Låt oss lägga till fler bilder

Att arbeta med fyra bilder är bra, men det skulle vara bättre om vi kan skala det till hur många bilder som helst. Detta är trots allt syftet med en bildskjutare. Vi borde kunna överväga N bilder.

För detta kommer vi att göra koden mer generisk genom att introducera Sass. Först definierar vi en variabel för antalet bilder ($n) och vi kommer att uppdatera varje del där vi hårdkodat antalet bilder (4).

Låt oss börja med förseningarna:

.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 */

Formeln för förseningen är (1 - $i)*duration/$n, vilket ger oss följande Sass-loop:

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

Vi kan också göra varaktigheten till en variabel om vi verkligen vill. Men låt oss gå vidare till animationen:

@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); }
}

Låt oss förenkla det för att få en bättre bild av mönstret:

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

Steget mellan varje stat är lika med 25% - vilket är 100%/4 — och vi lägger till en -90deg vinkel — vilket är -360deg/4. Det betyder att vi kan skriva vår loop så här istället:

@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); }
}

Eftersom varje bild tar 5% av animationen ändrar vi detta:

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

…med detta:

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

Det bör nämnas att 5% är ett godtyckligt värde jag väljer för detta exempel. Vi kan också göra det till en variabel för att styra hur lång tid varje bild ska vara synlig. Jag tänker hoppa över det för enkelhetens skull, men för läxor kan du försöka göra det och dela ditt genomförande i kommentarerna!

@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); }
}

Den sista biten är att uppdatera transform-origin. Vi kommer att behöva några geometriknep. Oavsett antalet bilder är konfigurationen alltid densamma. Vi har våra bilder (små cirklar) placerade i en stor cirkel och vi måste hitta värdet på radien, R.

Du vill förmodligen inte ha en tråkig geometriförklaring så här är hur vi hittar R:

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

Om vi ​​uttrycker det i procent ger det oss:

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

… vilket betyder transform-origin värdet är lika med:

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

Var gjort! Vi har ett reglage som fungerar med alla sifferbilder!

Låt oss slänga nio bilder där:

Lägg till så många bilder du vill och uppdatera $n variabel med det totala antalet bilder.

Inslagning upp

Med några få knep med hjälp av CSS-transformationer och standardgeometri skapade vi ett snyggt cirkulärt reglage som inte kräver mycket kod. Det som är coolt med det här reglaget är att vi inte behöver bry oss om att duplicera bilderna för att behålla den oändliga animationen eftersom vi har en cirkel. Efter en hel rotation kommer vi tillbaka till den första bilden!

Tidsstämpel:

Mer från CSS-tricks