Eine CSS-Slinky in 3D? Herausforderung  akzeptiert!

Avatar of Jhey Tompkins
Jhey Tompkins on

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Braydon Coyer hat kürzlich eine monatliche CSS-Kunstherausforderung ins Leben gerufen. Er hatte mich tatsächlich kontaktiert, um eine Kopie meines Buches Move Things with CSS als Preis für den Gewinner der Herausforderung zu spenden – was ich mehr als gerne getan habe!

Die Herausforderung des ersten Monats? Feder. Und als ich darüber nachdachte, was ich für die Herausforderung machen könnte, kamen mir sofort Slinkys in den Sinn. Sie kennen Slinkys, richtig? Dieses klassische Spielzeug, das man die Treppe hinunterwirft und es sich mit eigenem Schwung bewegt.

Animated Gif of a Slinky toy going down stairs.
Eine sich bewegende Slinky

Können wir eine Slinky schaffen, die so eine Treppe hinunterläuft, in CSS? Das ist genau die Art von Herausforderung, die ich mag, also dachte ich, wir könnten das gemeinsam in diesem Artikel angehen. Bereit zum Loslegen? (Wortspiel beabsichtigt.)

Einrichtung des Slinky-HTML

Lassen Sie uns das flexibel gestalten. (Kein Wortspiel beabsichtigt.) Ich meine damit, dass wir das Verhalten der Slinky über CSS-Benutzerdefinierte Eigenschaften steuern wollen, was uns die Flexibilität gibt, Werte zu ändern, wenn wir es brauchen.

Hier ist, wie ich die Szene aufbaue, kurz in Pug geschrieben

- const RING_COUNT = 10;
.container
  .scene
    .plane(style=`--ring-count: ${RING_COUNT}`)
      - let rings = 0;
      while rings < RING_COUNT
        .ring(style=`--index: ${rings};`)
        - rings++;

Diese Inline-Benutzerdefinierten Eigenschaften sind ein einfacher Weg für uns, die Anzahl der Ringe zu aktualisieren, und werden sich als nützlich erweisen, wenn wir tiefer in diese Herausforderung eintauchen. Der obige Code gibt uns 10 Ringe mit einem HTML, das kompiliert ungefähr so aussieht

<div class="container">
  <div class="scene">
    <div class="plane" style="--ring-count: 10">
      <div class="ring" style="--index: 0;"></div>
      <div class="ring" style="--index: 1;"></div>
      <div class="ring" style="--index: 2;"></div>
      <div class="ring" style="--index: 3;"></div>
      <div class="ring" style="--index: 4;"></div>
      <div class="ring" style="--index: 5;"></div>
      <div class="ring" style="--index: 6;"></div>
      <div class="ring" style="--index: 7;"></div>
      <div class="ring" style="--index: 8;"></div>
      <div class="ring" style="--index: 9;"></div>
    </div>
  </div>
</div>

Der ursprüngliche Slinky-CSS

Wir brauchen einige Stile! Wir wollen eine dreidimensionale Szene. Ich behalte im Auge, was wir später vielleicht tun wollen, deshalb denke ich darüber nach, eine zusätzliche Wrapper-Komponente mit einer .scene Klasse zu haben.

Beginnen wir mit der Definition einiger Eigenschaften für unsere „Infini-Slinky“-Szene

:root {
  --border-width: 1.2vmin;
  --depth: 20vmin;
  --stack-height: 6vmin;
  --scene-size: 20vmin;
  --ring-size: calc(var(--scene-size) * 0.6);
  --plane: radial-gradient(rgb(0 0 0 / 0.1) 50%, transparent 65%);
  --ring-shadow: rgb(0 0 0 / 0.5);
  --hue-one: 320;
  --hue-two: 210;
  --blur: 10px;
  --speed: 1.2s;
  --bg: #fafafa;
  --ring-filter: brightness(1) drop-shadow(0 0 0 var(--accent));
}

Diese Eigenschaften definieren die Merkmale unserer Slinky und der Szene. Bei den meisten 3D-CSS-Szenen setzen wir transform-style durchweg

* {
  box-sizing: border-box;
  transform-style: preserve-3d;
}

Jetzt brauchen wir Stile für unsere .scene. Der Trick besteht darin, die .plane zu transformieren, damit es so aussieht, als würde unsere CSS-Slinky unendlich eine Treppe hinunterlaufen. Ich musste ein bisschen spielen, um alles genau so zu bekommen, wie ich es wollte, also verzeihen Sie mir die magische Zahl für jetzt, sie wird später Sinn ergeben.

.container {
  /* Define the scene's dimensions */
  height: var(--scene-size);
  width: var(--scene-size);
  /* Add depth to the scene */
  transform:
    translate3d(0, 0, 100vmin)
    rotateX(-24deg) rotateY(32deg)
    rotateX(90deg)
    translateZ(calc((var(--depth) + var(--stack-height)) * -1))
    rotate(0deg);
}
.scene,
.plane {
  /* Ensure our container take up the full .container */
  height: 100%;
  width: 100%;
  position: relative;
}
.scene {
  /* Color is arbitrary */
  background: rgb(162 25 230 / 0.25);
}
.plane {
  /* Color is arbitrary */
  background: rgb(25 161 230 / 0.25);
  /* Overrides the previous selector */
  transform: translateZ(var(--depth));
}

Hier passiert eine ganze Menge mit der .container-Transformation. Insbesondere

  • translate3d(0, 0, 100vmin): Dies bringt den .container nach vorne und verhindert, dass unsere 3D-Arbeit vom Body abgeschnitten wird. Wir verwenden hier auf dieser Ebene keine perspective, daher können wir damit durchkommen.
  • rotateX(-24deg) rotateY(32deg): Dies dreht die Szene nach unseren Wünschen.
  • rotateX(90deg): Dies dreht den .container um eine Vierteldrehung, was die .scene und die .plane standardmäßig abflacht. Andernfalls würden die beiden Ebenen wie die Ober- und Unterseite eines 3D-Würfels aussehen.
  • translate3d(0, 0, calc((var(--depth) + var(--stack-height)) * -1)): Wir können dies verwenden, um die Szene zu verschieben und sie auf der y-Achse (nun ja, eigentlich der z-Achse) zu zentrieren. Dies liegt im Auge des Designers. Hier verwenden wir --depth und --stack-height, um Dinge zu zentrieren.
  • rotate(0deg): Obwohl derzeit nicht verwendet, möchten wir die Szene vielleicht später drehen oder die Drehung der Szene animieren.

Um zu visualisieren, was mit dem .container passiert, schauen Sie sich diese Demo an und tippen Sie irgendwo, um die angewendete transform zu sehen (nur für Chromium. 😭)

Jetzt haben wir eine gestylte Szene! 💪

Stilisierung der Slinky-Ringe

Hier werden diese CSS-Benutzerdefinierten Eigenschaften ihre Rolle spielen. Wir haben die Inline-Eigenschaften --index und --ring-count aus unserem HTML. Außerdem haben wir die vordefinierten Eigenschaften im CSS, die wir zuvor auf dem :root gesehen haben.

Die Inline-Eigenschaften spielen eine Rolle bei der Positionierung jedes Rings

.ring {
  --origin-z: 
    calc(
      var(--stack-height) - (var(--stack-height) / var(--ring-count)) 
      * var(--index)
    );
  --hue: var(--hue-one);
  --accent: hsl(var(--hue) 100% 55%);
  height: var(--ring-size);
  width: var(--ring-size);
  border-radius: 50%;
  border: var(--border-width) solid var(--accent);
  position: absolute;
  top: 50%;
  left: 50%;
  transform-origin: calc(100% + (var(--scene-size) * 0.2)) 50%;
  transform:
    translate3d(-50%, -50%, var(--origin-z))
    translateZ(0)
    rotateY(0deg);
}
.ring:nth-of-type(odd) {
  --hue: var(--hue-two);
}

Beachten Sie, wie wir den Wert von --origin-z berechnen und wie wir jeden Ring mit der transform-Eigenschaft positionieren. Das geschieht nach der Positionierung jedes Rings mit position: absolute.

Es ist auch erwähnenswert, wie wir die Farbe jedes Rings in der letzten Regel abwechseln. Als ich dies zum ersten Mal implementierte, wollte ich eine Regenbogen-Slinky erstellen, bei der die Ringe durch die Farbtöne gehen. Aber das fügt dem Effekt etwas Komplexität hinzu.

Jetzt haben wir einige Ringe auf unserer erhöhten .plane

Transformation der Slinky-Ringe

Es ist Zeit, Dinge in Bewegung zu setzen! Sie haben vielleicht bemerkt, dass wir für jeden .ring einen transform-origin wie diesen gesetzt haben

.ring {
  transform-origin: calc(100% + (var(--scene-size) * 0.2)) 50%;
}

Dies basiert auf der Größe der .scene. Dieser Wert von 0.2 ist die Hälfte der verbleibenden verfügbaren Größe der .scene, nachdem der .ring positioniert wurde.

Wir könnten das sicherlich ein wenig aufräumen!

:root {
  --ring-percentage: 0.6;
  --ring-size: calc(var(--scene-size) * var(--ring-percentage));
  --ring-transform:
    calc(
      100% 
      + (var(--scene-size) * ((1 - var(--ring-percentage)) * 0.5))
    ) 50%;
}

.ring {
  transform-origin: var(--ring-transform);
}

Warum dieser transform-origin? Nun, wir brauchen, dass der Ring so aussieht, als würde er sich außermittig bewegen. Das Spielen mit der transform eines einzelnen Rings ist eine gute Möglichkeit, die transform herauszufinden, die wir anwenden wollen. Bewegen Sie den Schieberegler in dieser Demo, um den Ring umdrehen zu sehen

Fügen Sie alle Ringe wieder hinzu und wir können den gesamten Stapel umdrehen!

Hmm, aber sie fallen nicht auf die nächste Stufe. Wie können wir jeden Ring an die richtige Stelle fallen lassen?

Nun, wir haben ein berechnetes --origin-z, also berechnen wir --destination-z, damit sich die Tiefe ändert, wenn die Ringe transform werden. Wenn wir einen Ring oben auf dem Stapel haben, sollte er nach dem Fallen unten landen. Wir können unsere benutzerdefinierten Eigenschaften verwenden, um ein Ziel für jeden Ring zu definieren

ring {
  --destination-z: calc(
    (
      (var(--depth) + var(--origin-z))
      - (var(--stack-height) - var(--origin-z))
    ) * -1
  );
  transform-origin: var(--ring-transform);
  transform:
    translate3d(-50%, -50%, var(--origin-z))
    translateZ(calc(var(--destination-z) * var(--flipped, 0)))
    rotateY(calc(var(--flipped, 0) * 180deg));
}

Versuchen Sie jetzt, den Stapel zu bewegen! Wir sind auf dem besten Weg. 🙌

Animieren der Ringe

Wir wollen, dass unser Ring umdreht und dann fällt. Ein erster Versuch könnte so aussehen

.ring {
  animation-name: slink;
  animation-duration: 2s;
  animation-fill-mode: both;
  animation-iteration-count: infinite;
}

@keyframes slink {
  0%, 5% {
    transform:
      translate3d(-50%, -50%, var(--origin-z))
      translateZ(0)
      rotateY(0deg);
  }
  25% {
    transform:
      translate3d(-50%, -50%, var(--origin-z))
      translateZ(0)
      rotateY(180deg);
  }
  45%, 100% {
    transform:
      translate3d(-50%, -50%, var(--origin-z))
      translateZ(var(--destination-z))
      rotateY(180deg);
  }
}

Och, das ist gar nicht richtig!

Aber das liegt nur daran, dass wir animation-delay nicht verwenden. Alle Ringe *slinken* gleichzeitig. Führen wir eine animation-delay basierend auf dem --index des Rings ein, damit sie nacheinander slinken.

.ring {
  animation-delay: calc(var(--index) * 0.1s);
}

Okay, das ist in der Tat „besser“. Aber das Timing ist immer noch nicht richtig. Was aber stärker auffällt, ist der Nachteil von animation-delay. Es wird nur bei der ersten Animationsiteration angewendet. Danach verlieren wir den Effekt.

An dieser Stelle färben wir die Ringe, damit sie den Farbkreis durchlaufen. Dies wird es einfacher machen, zu sehen, was vor sich geht.

.ring {
  --hue: calc((360 / var(--ring-count)) * var(--index));
}

Das ist besser! ✨

Zurück zum Problem. Da wir kein Delay angeben können, das für jede Iteration angewendet wird, können wir auch nicht den gewünschten Effekt erzielen. Für unsere Slinky könnten wir mit einem konsistenten animation-delay den gewünschten Effekt erzielen. Und wir könnten eine Keyframe verwenden und uns auf unsere gekapselten benutzerdefinierten Eigenschaften verlassen. Sogar eine animation-repeat-delay könnte eine interessante Ergänzung sein.

Diese Funktionalität ist in JavaScript-Animationslösungen verfügbar. GreenSock zum Beispiel erlaubt es Ihnen, ein delay und ein repeatDelay anzugeben.

Aber unser Slinky-Beispiel ist nicht die einfachste Sache, um dieses Problem zu veranschaulichen. Zerlegen wir das in ein einfaches Beispiel. Betrachten Sie zwei Boxen. Und Sie möchten, dass sie abwechselnd rotieren.

Wie machen wir das mit CSS und ohne „Tricks“? Eine Idee ist, einer der Boxen ein Delay hinzuzufügen

.box {
  animation: spin 1s var(--delay, 0s) infinite;
}
.box:nth-of-type(2) {
  --delay: 1s;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

Aber das wird nicht funktionieren, weil sich die rote Box weiter drehen wird. Und die blaue auch nach ihrem anfänglichen animation-delay.

Mit etwas wie GreenSock können wir jedoch den gewünschten Effekt mit relativer Leichtigkeit erzielen

import gsap from 'https://cdn.skypack.dev/gsap'

gsap.to('.box', {
  rotate: 360,
  /**
   * A function based value, means that the first box has a delay of 0 and
   * the second has a delay of 1
  */
  delay: (index) > index,
  repeatDelay: 1,
  repeat: -1,
  ease: 'power1.inOut',
})

Und da ist es!

Aber wie können wir das ohne JavaScript machen?

Nun, wir müssen unsere @keyframes „hacken“ und animation-delay komplett über Bord werfen. Stattdessen füllen wir die @keyframes mit leerem Raum. Das hat verschiedene Tücken, aber bauen wir erstmal einen neuen Keyframe. Dieser wird das Element zweimal vollständig drehen

@keyframes spin {
  50%, 100% {
    transform: rotate(360deg);
  }
}

Es ist, als hätten wir den Keyframe halbiert. Und jetzt müssen wir die animation-duration verdoppeln, um die gleiche Geschwindigkeit zu erreichen. Ohne animation-delay könnten wir versuchen, animation-direction: reverse auf der zweiten Box zu setzen

.box {
  animation: spin 2s infinite;
}

.box:nth-of-type(2) {
  animation-direction: reverse;
}

Fast.

Die Drehung ist falsch herum. Wir könnten ein Wrapper-Element verwenden und dieses drehen, aber das könnte knifflig werden, da es mehr auszubalancieren gibt. Der andere Ansatz ist, zwei Keyframes statt einem zu erstellen

@keyframes box-one {
  50%, 100% {
    transform: rotate(360deg);
  }
}
@keyframes box-two {
  0%, 50% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

Und da haben wir es

Das wäre viel einfacher gewesen, wenn wir die Repeat-Verzögerung mit so etwas angeben könnten

/* Hypothetical! */
animation: spin 1s 0s 1s infinite;

Oder wenn die wiederholte Verzögerung mit der anfänglichen Verzögerung übereinstimmen würde, könnten wir möglicherweise einen Kombinator dafür haben

/* Hypothetical! */
animation: spin 1s 1s+ infinite;

Es wäre auf jeden Fall eine interessante Ergänzung!

Also brauchen wir Keyframes für all diese Ringe?

Ja, das ist der Fall, wenn wir eine konsistente Verzögerung wünschen. Und das müssen wir tun, basierend darauf, was wir als Animationsfenster verwenden werden. Alle Ringe müssen „geslinkt“ und stabilisiert sein, bevor die Keyframes wiederholt werden.

Das wäre eine schreckliche Tipparbeit. Aber dafür haben wir CSS-Präprozessoren, richtig? Nun, zumindest bis wir Schleifen und einige zusätzliche benutzerdefinierte Eigenschaften im Web haben. 😉

Die heutige Waffe der Wahl wird Stylus sein. Es ist mein liebster CSS-Präprozessor und das schon seit einiger Zeit. Gewohnheit bedeutet, dass ich nicht zu Sass gewechselt bin. Außerdem mag ich die fehlende Grammatikpflicht und die Flexibilität von Stylus.

Gut, dass wir das nur einmal schreiben müssen

// STYLUS GENERATED KEYFRAMES BE HERE...
$ring-count = 10
$animation-window = 50
$animation-step = $animation-window / $ring-count

for $ring in (0..$ring-count)
  // Generate a set of keyframes based on the ring index
  // index is the ring
  $start = $animation-step * ($ring + 1)
  @keyframes slink-{$ring} {
    // In here is where we need to generate the keyframe steps based on ring count and window.
    0%, {$start * 1%} {
      transform
        translate3d(-50%, -50%, var(--origin-z))
        translateZ(0)
        rotateY(0deg)
    }
    // Flip without falling
    {($start + ($animation-window * 0.75)) * 1%} {
      transform
        translate3d(-50%, -50%, var(--origin-z))
        translateZ(0)
        rotateY(180deg)
    }
    // Fall until the cut-off point
    {($start + $animation-window) * 1%}, 100% {
      transform
        translate3d(-50%, -50%, var(--origin-z))
        translateZ(var(--destination-z))
        rotateY(180deg)
    }
  }

Hier sind die Bedeutungen dieser Variablen

  • $ring-count: Die Anzahl der Ringe in unserer Slinky.
  • $animation-window: Dies ist der Prozentsatz des Keyframes, über den wir slinken können. In unserem Beispiel sagen wir, dass wir über 50% der Keyframes slinken wollen. Die verbleibenden 50% sollen für Verzögerungen verwendet werden.
  • $animation-step: Dies ist die berechnete Staffelung für jeden Ring. Wir können diese verwenden, um die eindeutigen Keyframe-Prozentsätze für jeden Ring zu berechnen.

Hier sehen Sie, wie es zu CSS kompiliert wird, zumindest für die ersten paar Iterationen

Gesamten Code anzeigen
@keyframes slink-0 {
  0%, 4.5% {
    transform:
      translate3d(-50%, -50%, var(--origin-z))
      translateZ(0)
      rotateY(0deg);
  }
  38.25% {
    transform:
      translate3d(-50%, -50%, var(--origin-z))
      translateZ(0)
      rotateY(180deg);
  }
  49.5%, 100% {
    transform:
      translate3d(-50%, -50%, var(--origin-z))
      translateZ(var(--destination-z))
      rotateY(180deg);
  }
}
@keyframes slink-1 {
  0%, 9% {
    transform:
      translate3d(-50%, -50%, var(--origin-z))
      translateZ(0)
      rotateY(0deg);
  }
  42.75% {
    transform:
      translate3d(-50%, -50%, var(--origin-z))
      translateZ(0)
      rotateY(180deg);
  }
  54%, 100% {
    transform:
       translate3d(-50%, -50%, var(--origin-z))
       translateZ(var(--destination-z))
       rotateY(180deg);
  }
}

Das Letzte, was zu tun ist, ist, jeden Keyframe-Satz auf jeden Ring anzuwenden. Das können wir mit unserem Markup tun, wenn wir wollen, indem wir es aktualisieren, um sowohl einen --index *als auch* einen --name zu definieren

- const RING_COUNT = 10;
.container
  .scene
    .plane(style=`--ring-count: ${RING_COUNT}`)
      - let rings = 0;
      while rings < RING_COUNT
        .ring(style=`--index: ${rings}; --name: slink-${rings};`)
        - rings++;

Was uns kompiliert das hier ergibt

<div class="container">
  <div class="scene">
    <div class="plane" style="--ring-count: 10">
      <div class="ring" style="--index: 0; --name: slink-0;"></div>
      <div class="ring" style="--index: 1; --name: slink-1;"></div>
      <div class="ring" style="--index: 2; --name: slink-2;"></div>
      <div class="ring" style="--index: 3; --name: slink-3;"></div>
      <div class="ring" style="--index: 4; --name: slink-4;"></div>
      <div class="ring" style="--index: 5; --name: slink-5;"></div>
      <div class="ring" style="--index: 6; --name: slink-6;"></div>
      <div class="ring" style="--index: 7; --name: slink-7;"></div>
      <div class="ring" style="--index: 8; --name: slink-8;"></div>
      <div class="ring" style="--index: 9; --name: slink-9;"></div>
    </div>
  </div>
</div>

Und dann kann unser Styling entsprechend aktualisiert werden

.ring {
  animation: var(--name) var(--speed) both infinite cubic-bezier(0.25, 0, 1, 1);
}

Timing ist alles. Daher haben wir die Standard-animation-timing-function verworfen und verwenden eine cubic-bezier. Wir nutzen auch die --speed benutzerdefinierte Eigenschaft, die wir zu Beginn definiert haben.

Aw yeah. Jetzt haben wir eine sich bewegende CSS-Slinky! Spielen Sie mit einigen der Variablen im Code und sehen Sie, welches unterschiedliche Verhalten Sie erzielen können.

Erstellung einer unendlichen Animation

Nachdem wir den schwierigsten Teil hinter uns haben, können wir dafür sorgen, dass die Animation unendlich wiederholt wird. Dazu werden wir die Szene verschieben, während unsere Slinky slinkt, damit es so aussieht, als würde sie in ihre ursprüngliche Position zurückslinken.

.scene {
  animation: step-up var(--speed) infinite linear both;
}

@keyframes step-up {
  to {
    transform: translate3d(-100%, 0, var(--depth));
  }
}

Wow, das war mit sehr wenig Aufwand erledigt!

Wir können die Plattformfarben von .scene und .plane entfernen, um zu verhindern, dass die Animation zu schockierend ist

Fast fertig! Das Letzte, was zu beachten ist, ist, dass der Stapel der Ringe umdreht, bevor er wieder slinkt. Hier haben wir bereits erwähnt, dass die Verwendung von Farbe nützlich wäre. Ändern Sie die Anzahl der Ringe auf eine ungerade Zahl, wie 11, und schalten Sie wieder auf abwechselnde Ringfarbe um

Boom! Wir haben eine funktionierende CSS-Slinky! Sie ist auch konfigurierbar!

Lustige Variationen

Wie wäre es mit einem „Flip-Flop“-Effekt? Damit meine ich, dass die Slinky abwechselnd in verschiedene Richtungen slinkt. Wenn wir der Szene ein zusätzliches Wrapper-Element hinzufügen, könnten wir die Szene bei jedem Slink um 180 Grad drehen.

- const RING_COUNT = 11;
.container
  .flipper
    .scene
      .plane(style=`--ring-count: ${RING_COUNT}`)
        - let rings = 0;
        while rings < RING_COUNT
          .ring(style=`--index: ${rings}; --name: slink-${rings};`)
          - rings++;

Was die Animation angeht, können wir die steps() Timing-Funktion verwenden und die doppelte --speed nehmen

.flipper {
  animation: flip-flop calc(var(--speed) * 2) infinite steps(1);
  height: 100%;
  width: 100%;
}

@keyframes flip-flop {
  0% {
    transform: rotate(0deg);
  }
  50% {
    transform: rotate(180deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

Zu guter Letzt ändern wir die Art und Weise, wie die step-up-Animation des .scene-Elements funktioniert. Sie muss sich nicht mehr auf der x-Achse bewegen.

@keyframes step-up {
  0% {
    transform: translate3d(-50%, 0, 0);
  }
  100% {
    transform: translate3d(-50%, 0, var(--depth));
  }
}

Beachten Sie die verwendete animation-timing-function. Diese Verwendung von steps(1) macht es möglich.

Wenn Sie eine weitere lustige Verwendung von steps() wünschen, schauen Sie sich diesen #SpeedyCSSTip an!

Für einen zusätzlichen Touch könnten wir die gesamte Szene langsam drehen

.container {
  animation: rotate calc(var(--speed) * 40) infinite linear;
}
@keyframes rotate {
  to {
    transform:
      translate3d(0, 0, 100vmin)
      rotateX(-24deg)
      rotateY(-32deg)
      rotateX(90deg)
      translateZ(calc((var(--depth) + var(--stack-height)) * -1))
      rotate(360deg);
  }
}

Das gefällt mir! Stil ist natürlich subjektiv... also habe ich eine kleine App erstellt, mit der Sie Ihre Slinky konfigurieren können

Und hier sind die „Original“- und „Flip-Flop“-Versionen, die ich mit Schatten und Themengestaltung etwas weiter verfeinert habe.

Endgültige Demos

Das ist alles!

Das ist zumindest eine Möglichkeit, eine reine CSS-Slinky zu erstellen, die sowohl 3D als auch konfigurierbar ist. Sicher, Sie werden vielleicht nicht jeden Tag auf etwas wie das zurückgreifen, aber es bringt interessante CSS-Animationstechniken hervor. Es wirft auch die Frage auf, ob eine animation-repeat-delay-Eigenschaft in CSS nützlich wäre. Was denken Sie? Glauben Sie, dass es gute Anwendungsfälle dafür gäbe? Das würde ich gerne wissen.

Spielen Sie auf jeden Fall mit dem Code herum – alles ist in dieser CodePen-Sammlung verfügbar!