Λογότυπο Zephyrnet

Ένα CSS Slinky σε 3D; Η πρόκληση έγινε δεκτή!

Ημερομηνία:

Μπρέιντον Κόγιερ πρόσφατα ξεκίνησε ένα μηνιαίο CSS καλλιτεχνική πρόκληση. Στην πραγματικότητα είχε επικοινωνήσει μαζί μου για τη δωρεά ενός αντιγράφου του βιβλίου μου Μετακινήστε πράγματα με CSS να το χρησιμοποιήσω ως έπαθλο για τον νικητή της πρόκλησης — κάτι που χάρηκα περισσότερο!

Η πρόκληση του πρώτου μήνα; Άνοιξη. Και όταν σκεφτόταν τι να κάνει για την πρόκληση, ο Slinkys ήρθε αμέσως στο μυαλό. Ξέρεις τον Slinkys, σωστά; Αυτό το κλασικό παιχνίδι που γκρεμίζεις τις σκάλες και ταξιδεύει με τη δική του ορμή.

Ένα slinking Slinky

Μπορούμε να δημιουργήσουμε ένα Slinky που κατεβαίνει τις σκάλες όπως αυτό στο CSS; Αυτό ακριβώς είναι το είδος της πρόκλησης που μου αρέσει, οπότε σκέφτηκα ότι θα μπορούσαμε να το αντιμετωπίσουμε μαζί σε αυτό το άρθρο. Έτοιμοι να κυλήσουν; (Προορίζεται λογοπαίγνιο.)

Ρύθμιση του Slinky HTML

Ας το κάνουμε αυτό ευέλικτο. (Χωρίς λογοπαίγνιο.) Αυτό που εννοώ με αυτό είναι ότι θέλουμε να μπορούμε να ελέγχουμε τη συμπεριφορά του Slinky μέσω προσαρμοσμένων ιδιοτήτων CSS, δίνοντάς μας την ευελιξία να εναλλάσσουμε τιμές όταν χρειάζεται.

Να πώς στήνω τη σκηνή, γραμμένο σε Pug για συντομία:

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

Αυτές οι ενσωματωμένες προσαρμοσμένες ιδιότητες είναι ένας εύκολος τρόπος για να ενημερώσουμε τον αριθμό των δαχτυλιδιών και θα μας φανούν χρήσιμοι καθώς εμβαθύνουμε σε αυτήν την πρόκληση. Ο παραπάνω κώδικας μας δίνει 10 κουδουνίζει με HTML που φαίνεται κάτι σαν αυτό όταν συντάσσεται:

<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>

Το αρχικό Slinky CSS

Θα χρειαστούμε μερικά στυλ! Αυτό που θέλουμε είναι ένα τρισδιάστατη σκηνή. Έχω υπόψη μου κάποια πράγματα που μπορεί να θέλουμε να κάνουμε αργότερα, οπότε αυτό είναι το σκεπτικό πίσω από το να έχουμε ένα επιπλέον εξάρτημα περιτυλίγματος με .scene τάξη.

Ας ξεκινήσουμε ορίζοντας ορισμένες ιδιότητες για τη σκηνή μας "infini-slinky":

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

Αυτές οι ιδιότητες καθορίζουν τα χαρακτηριστικά του Slinky μας και της σκηνής. Με την πλειονότητα των 3D σκηνών CSS, θα σκηνοθετήσουμε transform-style σε όλους τους τομείς:

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

Τώρα χρειαζόμαστε στυλ για το δικό μας .scene. Το κόλπο είναι να μεταφράσεις το .plane οπότε φαίνεται ότι το CSS Slinky μας κατεβαίνει απεριόριστα μια σκάλα. Έπρεπε να παίζω για να φέρω τα πράγματα όπως ακριβώς θέλω, οπότε αντέξου με τον μαγικό αριθμό προς το παρόν, καθώς θα έχουν νόημα αργότερα.

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

Υπάρχει ένα δίκαιο κομμάτι που συμβαίνει εδώ με το .container μεταμόρφωση. ΕΙΔΙΚΑ:

  • translate3d(0, 0, 100vmin): Αυτό φέρνει το .container προς τα εμπρός και εμποδίζει την αποκοπή της τρισδιάστατης εργασίας μας από το σώμα. Δεν χρησιμοποιούμε perspective σε αυτό το επίπεδο, ώστε να μπορούμε να το ξεφύγουμε.
  • rotateX(-24deg) rotateY(32deg): Αυτό περιστρέφει τη σκηνή με βάση τις προτιμήσεις μας.
  • rotateX(90deg): Αυτό περιστρέφει το .container κατά ένα τέταρτο στροφή, που ισοπεδώνει το .scene και .plane από προεπιλογή, Διαφορετικά, τα δύο στρώματα θα μοιάζουν με το επάνω και το κάτω μέρος ενός κύβου 3D.
  • translate3d(0, 0, calc((var(--depth) + var(--stack-height)) * -1)): Μπορούμε να το χρησιμοποιήσουμε για να μετακινήσουμε τη σκηνή και να την κεντράρουμε στον άξονα y (καλά, στην πραγματικότητα στον άξονα z). Αυτό είναι στο μάτι του σχεδιαστή. Εδώ, χρησιμοποιούμε το --depth και --stack-height να κεντράρουν τα πράγματα.
  • rotate(0deg): Αν και, δεν χρησιμοποιείται αυτή τη στιγμή, μπορεί να θέλουμε να περιστρέψουμε τη σκηνή ή να κάνουμε κίνηση στην περιστροφή της σκηνής αργότερα.

Για να οπτικοποιήσετε τι συμβαίνει με το .container, ελέγξτε αυτό το demo και πατήστε οπουδήποτε για να το δείτε transform εφαρμόστηκε (συγγνώμη, μόνο Chromium. 😭):

Τώρα έχουμε μια στιλιστική σκηνή! 💪

Στυλ στα δαχτυλίδια του Slinky

Εδώ θα παίξουν τον ρόλο τους αυτές οι προσαρμοσμένες ιδιότητες CSS. Έχουμε τις ενσωματωμένες ιδιότητες --index και --ring-count από το HTML μας. Έχουμε επίσης τις προκαθορισμένες ιδιότητες στο CSS που είδαμε νωρίτερα στο :root.

Οι ενσωματωμένες ιδιότητες θα παίξουν ρόλο στην τοποθέτηση κάθε δακτυλίου:

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

Λάβετε υπόψη πώς υπολογίζουμε το --origin-z αξία καθώς και πώς τοποθετούμε κάθε δακτύλιο με το transform ιδιοκτησία. Αυτό έρχεται μετά την τοποθέτηση κάθε δακτυλίου με position: absolute .

Αξίζει επίσης να σημειωθεί πώς εναλλάσσουμε το χρώμα κάθε δαχτυλιδιού σε αυτό το τελευταίο σύνολο κανόνων. Όταν το εφάρμοσα για πρώτη φορά, ήθελα να δημιουργήσω ένα ουράνιο τόξο slinky όπου οι δακτύλιοι περνούσαν από τις αποχρώσεις. Αλλά αυτό προσθέτει λίγη πολυπλοκότητα στο αποτέλεσμα.

Τώρα έχουμε μερικά δαχτυλίδια στο raised μας .plane:

Μεταμόρφωση των δακτυλίων Slinky

Ήρθε η ώρα να βάλετε τα πράγματα σε κίνηση! Ίσως έχετε παρατηρήσει ότι ορίσαμε α transform-origin σε καθε .ring σαν αυτό:

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

Αυτό βασίζεται στο .scene Μέγεθος. Οτι 0.2 η τιμή είναι το ήμισυ του υπόλοιπου διαθέσιμου μεγέθους του .scene μετά τη .ring τοποθετείται.

Θα μπορούσαμε να το τακτοποιήσουμε λίγο σίγουρα!

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

Γιατί αυτό transform-origin? Λοιπόν, χρειαζόμαστε το δαχτυλίδι για να φαίνεται ότι κινείται εκτός κέντρου. Παίζοντας με το transform ενός μεμονωμένου δαχτυλιδιού είναι ένας καλός τρόπος για να επιλύσετε το transform θέλουμε να κάνουμε αίτηση. Μετακινήστε το ρυθμιστικό σε αυτήν την επίδειξη για να δείτε την αναστροφή του δακτυλίου:

Προσθέστε όλα τα δαχτυλίδια πίσω και μπορούμε να αναστρέψουμε ολόκληρη τη στοίβα!

Χμμ, αλλά δεν πέφτουν στην επόμενη σκάλα. Πώς μπορούμε να κάνουμε κάθε δαχτυλίδι να πέσει στη σωστή θέση;

Λοιπόν, έχουμε ένα υπολογισμένο --origin-z, ας υπολογίσουμε λοιπόν --destination-z οπότε το βάθος αλλάζει καθώς οι δακτύλιοι transform. Εάν έχουμε ένα δαχτυλίδι στην κορυφή της στοίβας, θα πρέπει να τυλίγεται στο κάτω μέρος αφού πέσει. Μπορούμε να χρησιμοποιήσουμε τις προσαρμοσμένες μας ιδιότητες για να ορίσουμε έναν προορισμό για κάθε δακτύλιο:

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

Τώρα δοκιμάστε να μετακινήσετε τη στοίβα! Φτάνουμε εκεί. 🙌

Εμψύχωση των δαχτυλιδιών

Θέλουμε το δαχτυλίδι μας να γυρίσει και μετά να πέσει. Μια πρώτη προσπάθεια μπορεί να μοιάζει κάπως έτσι:

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

Ουφ, αυτό δεν είναι καθόλου σωστό!

Αλλά αυτό συμβαίνει μόνο επειδή δεν χρησιμοποιούμε animation-delay. Όλα τα δαχτυλίδια είναι, χμ, γλιστρώντας Την ίδια στιγμή. Ας εισαγάγουμε ένα animation-delay με βάση τη --index του δαχτυλιδιού ώστε να γλιστρούν διαδοχικά.

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

Εντάξει, αυτό είναι πράγματι "καλύτερο". Αλλά το timing είναι ακόμα κλειστό. Αυτό που ξεχωρίζει περισσότερο, όμως, είναι το μειονέκτημα του animation-delay. Εφαρμόζεται μόνο στην πρώτη επανάληψη κινουμένων σχεδίων. Μετά από αυτό, χάνουμε το αποτέλεσμα.

Σε αυτό το σημείο, ας χρωματίσουμε τους δακτυλίους έτσι ώστε να προχωρούν μέσα από τον τροχό απόχρωσης. Αυτό θα κάνει πιο εύκολο να δούμε τι συμβαίνει.

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

Αυτό είναι καλύτερο! ✨

Επιστροφή στο θέμα. Επειδή δεν μπορούμε να καθορίσουμε μια καθυστέρηση που εφαρμόζεται σε κάθε επανάληψη, δεν μπορούμε επίσης να λάβουμε το αποτέλεσμα που θέλουμε. Για τον Slinky μας, αν μπορούσαμε να έχουμε μια συνεπή animation-delay, ίσως μπορέσουμε να επιτύχουμε το αποτέλεσμα που θέλουμε. Και θα μπορούσαμε να χρησιμοποιήσουμε ένα βασικό καρέ ενώ βασιζόμαστε στις προσαρμοσμένες ιδιότητες εύρους. Ακόμη και ένα animation-repeat-delay θα μπορούσε να είναι μια ενδιαφέρουσα προσθήκη.

Αυτή η λειτουργία είναι διαθέσιμη σε λύσεις κινούμενων εικόνων JavaScript. Για παράδειγμα, το GreenSock σάς επιτρέπει να καθορίσετε ένα delay και σε έναν repeatDelay.

Όμως, το παράδειγμά μας Slinky δεν είναι το πιο εύκολο πράγμα για να επεξηγήσει αυτό το πρόβλημα. Ας το αναλύσουμε σε ένα βασικό παράδειγμα. Εξετάστε δύο κουτιά. Και θέλετε να εναλλάσσονται το γύρισμα.

Πώς το κάνουμε αυτό με CSS και χωρίς «κόλπα»; Μια ιδέα είναι να προσθέσετε μια καθυστέρηση σε ένα από τα πλαίσια:

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

Όμως, αυτό δεν θα λειτουργήσει γιατί το κόκκινο κουτί θα συνεχίσει να περιστρέφεται. Και το ίδιο και το μπλε μετά το αρχικό του animation-delay.

Με κάτι σαν GreenSock, ωστόσο, μπορούμε να πετύχουμε το αποτέλεσμα που θέλουμε με σχετική ευκολία:

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',
})

Και εκεί είναι!

Αλλά πώς μπορούμε να το κάνουμε αυτό χωρίς JavaScript;

Λοιπόν, πρέπει να "χακάρουμε" το δικό μας @keyframes και να καταργηθεί εντελώς animation-delay. Αντίθετα, θα ξεπεράσουμε το @keyframes με κενό χώρο. Αυτό συνοδεύεται από διάφορες ιδιορρυθμίες, αλλά ας προχωρήσουμε και ας δημιουργήσουμε πρώτα ένα νέο βασικό καρέ. Αυτό θα περιστρέψει πλήρως το στοιχείο δύο φορές:

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

Είναι σαν να έχουμε κόψει το πλαίσιο στη μέση. Και τώρα θα πρέπει να διπλασιάσουμε το animation-duration να πάρει την ίδια ταχύτητα. Χωρίς χρήση animation-delay, θα μπορούσαμε να δοκιμάσουμε τη ρύθμιση animation-direction: reverse στο δεύτερο κουτί:

.box {
  animation: spin 2s infinite;
}

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

Σχεδόν.

Η περιστροφή είναι λάθος. Θα μπορούσαμε να χρησιμοποιήσουμε ένα στοιχείο περιτυλίγματος και να το περιστρέψουμε, αλλά αυτό θα μπορούσε να γίνει δύσκολο καθώς υπάρχουν περισσότερα πράγματα για ισορροπία. Η άλλη προσέγγιση είναι να δημιουργήσετε δύο βασικά καρέ αντί για ένα:

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

Και να το έχουμε:

Αυτό θα ήταν πολύ πιο εύκολο αν είχαμε έναν τρόπο να καθορίσουμε την καθυστέρηση επανάληψης με κάτι σαν αυτό:

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

Ή αν η επαναλαμβανόμενη καθυστέρηση ταίριαζε με την αρχική καθυστέρηση, θα μπορούσαμε ενδεχομένως να έχουμε έναν συνδυασμό για αυτήν:

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

Σίγουρα θα ήταν μια ενδιαφέρουσα προσθήκη!

Χρειαζόμαστε λοιπόν βασικά καρέ για όλα αυτά τα δαχτυλίδια;

Ναι, δηλαδή, αν θέλουμε μια σταθερή καθυστέρηση. Και πρέπει να το κάνουμε αυτό με βάση αυτό που πρόκειται να χρησιμοποιήσουμε ως παράθυρο κινούμενης εικόνας. Όλοι οι δακτύλιοι πρέπει να έχουν «κλιθεί» και να έχουν σταθεροποιηθεί πριν επαναληφθούν τα βασικά καρέ.

Αυτό θα ήταν φρικτό να το γράψω με το χέρι. Αλλά αυτός είναι ο λόγος που έχουμε προεπεξεργαστές CSS, σωστά; Λοιπόν, τουλάχιστον μέχρι να λάβουμε βρόχους και μερικές επιπλέον προσαρμοσμένες δυνατότητες ιδιοκτησίας στον ιστό. 😉

Το σημερινό όπλο επιλογής θα είναι το Stylus. Είναι ο αγαπημένος μου προεπεξεργαστής CSS και είναι εδώ και αρκετό καιρό. Συνήθεια σημαίνει ότι δεν έχω μετακομίσει στο Sass. Επιπλέον, μου αρέσει η έλλειψη απαιτούμενης γραμματικής και ευελιξίας του Stylus.

Καλά που χρειάζεται να γράψουμε μόνο μια φορά:

// 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)
    }
  }

Δείτε τι σημαίνουν αυτές οι μεταβλητές:

  • $ring-count: Ο αριθμός των δαχτυλιδιών στο slinky μας.
  • $animation-window: Αυτό είναι το ποσοστό του βασικού καρέ στο οποίο μπορούμε να εισχωρήσουμε. Στο παράδειγμά μας, λέμε ότι θέλουμε να γλιστρήσουμε 50% των βασικών καρέ. Το υπόλοιπο 50% πρέπει να συνηθίσει για καθυστερήσεις.
  • $animation-step: Αυτή είναι η υπολογιζόμενη κλιμάκωση για κάθε δαχτυλίδι. Μπορούμε να το χρησιμοποιήσουμε για να υπολογίσουμε τα μοναδικά ποσοστά βασικών καρέ για κάθε δακτύλιο.

Δείτε πώς μεταγλωττίζεται σε CSS, τουλάχιστον για τις πρώτες δύο επαναλήψεις:

Δείτε τον πλήρη κωδικό
@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);
  }
}

Το τελευταίο πράγμα που πρέπει να κάνετε είναι να εφαρμόσετε κάθε σετ βασικών καρέ σε κάθε δακτύλιο. Μπορούμε να το κάνουμε αυτό χρησιμοποιώντας τη σήμανση μας, αν θέλουμε, ενημερώνοντάς το για να ορίσουμε και τα δύο an --index και a --name:

- 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++;

Το οποίο μας δίνει αυτό κατά τη σύνταξη:

<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>

Και τότε το στυλ μας μπορεί να ενημερωθεί ανάλογα:

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

Ο συγχρονισμός είναι το παν. Άρα παρακάμψαμε την προεπιλογή animation-timing-function και χρησιμοποιούμε α cubic-bezier. Χρησιμοποιούμε επίσης το --speed προσαρμοσμένη ιδιότητα που ορίσαμε στην αρχή.

Α ναι. Τώρα έχουμε ένα slinking CSS Slinky! Παίξτε με μερικές από τις μεταβλητές στον κώδικα και δείτε ποια διαφορετική συμπεριφορά μπορείτε να αποδώσετε.

Δημιουργώντας ένα άπειρο animation

Τώρα που έχουμε το πιο δύσκολο κομμάτι, μπορούμε να το φτάσουμε εκεί όπου η κινούμενη εικόνα επαναλαμβάνεται άπειρα. Για να το κάνουμε αυτό, θα μεταφράσουμε τη σκηνή ως το Slinky μας slinks έτσι ώστε να φαίνεται σαν να γέρνει πίσω στην αρχική του θέση.

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

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

Ουάου, χρειάστηκε πολύ λίγη προσπάθεια!

Μπορούμε να αφαιρέσουμε τα χρώματα της πλατφόρμας .scene και .plane για να αποτρέψετε το να είναι πολύ ενοχλητικό το κινούμενο σχέδιο:

Σχεδόν τελείωσα! Το τελευταίο πράγμα που πρέπει να αντιμετωπιστεί είναι ότι η στοίβα των δαχτυλιδιών ανατρέπεται πριν γλιστρήσει ξανά. Εδώ αναφέραμε προηγουμένως ότι η χρήση του χρώματος θα ήταν χρήσιμη. Αλλάξτε τον αριθμό των δαχτυλιδιών σε μονό αριθμό, όπως 11και επιστρέψτε στην εναλλαγή του χρώματος του δακτυλίου:

Κεραία! Έχουμε ένα λειτουργικό CSS slinky! Είναι επίσης διαμορφώσιμο!

Διασκεδαστικές παραλλαγές

Τι θα λέγατε για ένα εφέ "flip flop"; Με αυτό, εννοώ να βάλω το Slink να slink εναλλακτικούς τρόπους. Εάν προσθέσουμε ένα επιπλέον στοιχείο περιτυλίγματος στη σκηνή, θα μπορούσαμε να περιστρέψουμε τη σκηνή κατά 180deg σε κάθε κοίλωμα.

- 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++;

Όσον αφορά τα κινούμενα σχέδια, μπορούμε να χρησιμοποιήσουμε το steps() λειτουργία χρονισμού και χρήση διπλά --speed:

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

Τελευταίο, αλλά όχι λιγότερο σημαντικό, ας αλλάξουμε τον τρόπο .scene στοιχεία step-up έργα κινουμένων σχεδίων. Δεν χρειάζεται πλέον να κινείται στον άξονα x.

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

Σημειώστε το animation-timing-function που χρησιμοποιούμε. Αυτή η χρήση του steps(1) είναι αυτό που το κάνει δυνατό.

Εάν θέλετε μια άλλη διασκεδαστική χρήση του steps(), ελέγξτε αυτό #SpeedyCSSTip!

Για μια επιπλέον πινελιά, θα μπορούσαμε να περιστρέψουμε όλη τη σκηνή αργά:

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

Μου αρέσει! Φυσικά, το στυλ είναι υποκειμενικό… έτσι, έφτιαξα μια μικρή εφαρμογή που μπορείτε να χρησιμοποιήσετε για να διαμορφώσετε το Slinky σας:

Και εδώ είναι οι εκδόσεις "Original" και "Flip-Flop" που προχώρησα λίγο πιο πέρα ​​με τις σκιές και το θέμα.

Τελικές επιδείξεις

Αυτό είναι!

Αυτός είναι τουλάχιστον ένας τρόπος για να φτιάξετε ένα καθαρό CSS Slinky που να είναι και τρισδιάστατο και με δυνατότητα διαμόρφωσης. Σίγουρα, μπορεί να μην προσεγγίζετε κάτι τέτοιο κάθε μέρα, αλλά αναδεικνύει ενδιαφέρουσες τεχνικές κινούμενων σχεδίων CSS. Εγείρει επίσης το ερώτημα εάν έχοντας α animation-repeat-delay Η ιδιοκτησία στο CSS θα ήταν χρήσιμη. Τι νομίζετε; Πιστεύετε ότι θα υπήρχαν κάποιες καλές περιπτώσεις χρήσης; Θα ήθελα να μάθω.

Βεβαιωθείτε ότι έχετε παίξει με τον κωδικό — όλος είναι διαθέσιμος αυτή τη Συλλογή CodePen!

spot_img

Τελευταία Νοημοσύνη

spot_img

Συνομιλία με μας

Γεια σου! Πώς μπορώ να σε βοηθήσω?