Erstellung eines animierten Anmeldeformulars für TouchID

Avatar of Kirill Kiyutin
Kirill Kiyutin am

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

Ich bin vor einiger Zeit auf diesen erstaunlichen Dribbble-Shot von Jakub Reis gestoßen. Er hat meine Aufmerksamkeit erregt und ich wusste, dass ich ihn unbedingt in Code nachbilden musste. Damals wusste ich nicht wie. Ich habe viele verschiedene Dinge ausprobiert und etwa ein Jahr später habe ich es endlich geschafft, diese Demo zu erstellen.

Ich habe auf dem Weg ein paar Dinge gelernt, also lassen Sie mich Sie auf eine kleine Reise mitnehmen, was ich getan habe, um dies zu erstellen, weil Sie vielleicht auch das eine oder andere lernen.

Sehen Sie den Pen Öffnungsbildschirm für eine Banking-App von Kirill Kiyutin (@kiyutink) auf CodePen.

Schritt 1: Teilen Sie die Arbeit in Teile auf

Ich habe das Original-GIF viele Male angesehen. Mein Ziel war es, die Animation in kleine, verdauliche Häppchen zu zerlegen, und ich konnte sie wie folgt aufteilen:

Ich weiß, es sieht viel aus – aber wir können das schaffen!

Schritt 2: Nehmen Sie die Original-Demo Frame für Frame auseinander

Ich musste so viele Informationen wie möglich aus dem Original-GIF extrahieren, um ein gutes Verständnis der Animation zu bekommen. Daher habe ich es in einzelne Frames zerlegt. Es gibt tatsächlich viele Dienste, die das für uns tun können. Ich habe einen auf ezgif.com verwendet, aber es könnte genauso gut etwas anderes gewesen sein. So oder so ermöglicht uns dies, Details wie Farben, Größen und Proportionen aller verschiedenen Elemente zu erhalten, die wir erstellen müssen.

Oh, und wir müssen den Fingerabdruck immer noch in ein SVG umwandeln. Auch hier gibt es genügend Apps, die uns dabei helfen. Ich habe Adobe Illustrator verwendet, um den Fingerabdruck mit dem Zeichenstift zu verfolgen, um diesen Pfadsatz zu erhalten

Sehen Sie den Pen css-t. Pfade von Kirill Kiyutin (@kiyutink) auf CodePen.

Wir werden den gleichen Prozess mit dem Liniendiagramm durchlaufen, das gegen Ende der Animation erscheint, also können wir diesen Vektoreditor geöffnet lassen. 🙂

Schritt 3: Implementieren Sie die Animationen

Ich werde erklären, wie die Animationen im endgültigen Pen funktionieren, aber Sie können auch einige der erfolglosen Ansätze, die ich im Laufe der Zeit verfolgt habe, am Ende des Artikels finden.

Ich werde mich hier auf die wichtigen Teile konzentrieren und Sie können sich für den vollständigen Code auf die Demos beziehen.

Füllen des Fingerabdrucks

Lassen Sie uns die HTML-Struktur des Telefonbildschirms und des Fingerabdrucks erstellen.

<div class="demo">
  <div class="demo__screen demo__screen--clickable">
    <svg class="demo__fprint" viewBox="0 0 180 320">  
      <!-- removes-forwards and removes-backwards classes will be helpful later on -->
      <path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--pinkish" d="M46.1,214.3c0,0-4.7-15.6,4.1-33.3"/>
      <path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--purplish" d="M53.5,176.8c0,0,18.2-30.3,57.5-13.7"/>
      <path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--pinkish" d="M115.8,166.5c0,0,19.1,8.7,19.6,38.4"/>
      <!-- ... and about 20 more paths like this -->
    </svg>
  

Die Stile sind bisher ziemlich einfach. Beachten Sie, dass ich im gesamten Demo Sass verwende – ich finde, es hilft, die Arbeit sauber zu halten und erleichtert einige der schwereren Arbeiten, die wir tun müssen.

// I use a $scale variable to quickly change the scaling of the whole pen, so I can focus on the animation and decide on the size later on.
$scale: 1.65;
$purplish-color: #8742cc;
$pinkish-color: #a94a8c;
$bg-color: #372546;

// The main container
.demo {
  background: linear-gradient(45deg, lighten($pinkish-color, 10%), lighten($purplish-color, 10%));
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 0;
  user-select: none;
  overflow: hidden;
  position: relative;
  
  // The screen that holds the login component
  &__screen {
    position: relative;
    background-color: $bg-color;
    overflow: hidden;
    flex-shrink: 0;
    &--clickable {
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
  }
  
  // Styles the fingerprint SVG paths
  &__fprint-path {
    stroke-width: 2.5px;
    stroke-linecap: round;
    fill: none;
    stroke: white;
    visibility: hidden;
    transition: opacity 0.5s ease;
    
    &--pinkish {
      stroke: $pinkish-color;
    }
    
    &--purplish {
      stroke: $purplish-color;
    }    
  }
  
  // Sizes positions the fingerprint SVG
  &__fprint {
    width: 180px * $scale;
    height: 320px * $scale;
    position: relative;
    top: 20px * $scale;
    overflow: visible;
    // This is going to serve as background to show "unfilled" paths. we're gonna remove it at the moment where the filling animation is over
    background-image: url('https://kiyutink.github.io/svg/fprintBackground.svg');
    background-size: cover;
    
    &--no-bg {
      background-image: none;
    }
  }
}

Nun zum schwierigen Teil: den Fingerabdruck interaktiv zu machen. Sie können hier über die Animation von SVG-Linien lesen. Das ist die Methode, die wir verwenden werden, um jeden einzelnen Pfad zu füllen.

Lassen Sie uns eine Klasse erstellen, die ein Pfadelement beschreibt, damit die Pfade später einfacher manipuliert werden können.

class Path {
  constructor(selector, index) {
    this.index = index;
    this.querySelection = document.querySelectorAll(selector)[index];
    this.length = this.querySelection.getTotalLength();
    this.$ = $(selector).eq(index);
    this.setDasharray();
    this.removesForwards = this.$.hasClass('demo__fprint-path--removes-forwards');
  }
  
  setDasharray() {
    this.$.css('stroke-dasharray', `${this.length} ${this.length + 2}`);
    return this;
  }
  
  offset(ratio) {
    this.$.css('stroke-dashoffset', -this.length * ratio + 1);
    return this;
  }
  
  makeVisible() {
    this.$.css('visibility', 'visible');
    return this;
  }
}

Die allgemeine Idee ist folgende: Erstellen Sie eine Instanz dieser Klasse für jeden Pfad, den wir im Fingerabdruck haben, und modifizieren Sie ihn in jedem Frame. Die Pfade beginnen mit einem Offset-Verhältnis von -1 (vollständig unsichtbar) und erhöhen dann das Offset-Verhältnis (das wir von nun an als "Offset" bezeichnen werden) in jedem Frame um einen konstanten Wert, bis sie 0 (vollständig sichtbar) erreichen. Die Füllanimation ist zu diesem Zeitpunkt beendet.

Wenn Sie noch nie etwas mit diesem Frame-für-Frame-Ansatz animiert haben, hier ist eine sehr einfache Demo, die Ihnen hilft zu verstehen, wie das funktioniert

Sehen Sie den Pen 60fps raf Animationsproof of Concept von Kirill Kiyutin (@kiyutink) auf CodePen.

Wir sollten auch den Fall behandeln, dass der Benutzer aufhört zu tippen oder die Maustaste gedrückt hält. In diesem Fall animieren wir in die entgegengesetzte Richtung (wir subtrahieren in jedem Frame einen konstanten Wert vom Offset, bis er wieder -1 erreicht).

Lassen Sie uns die Funktion erstellen, die die Offset-Inkremente für jeden Frame berechnet – das wird später nützlich sein.

function getPropertyIncrement(startValue, endValue, transitionDuration) {
  // We animate at 60 fps
  const TICK_TIME = 1000 / 60;
  const ticksToComplete = transitionDuration / TICK_TIME;
  return (endValue - startValue) / ticksToComplete;
}

Nun ist es Zeit zu animieren! Wir werden die Fingerabdruckpfade in einem einzigen Array behalten

let fprintPaths = [];

// We create an instance of Path for every existing path. 
// We don't want the paths to be visible at first and then 
// disappear after the JavaScript runs, so we set them to 
// be invisible in CSS. That way we can offset them first 
// and then make them visible.
for (let i = 0; i < $(fprintPathSelector).length; i++) {
  fprintPaths.push(new Path(fprintPathSelector, i));
  fprintPaths[i].offset(-1).makeVisible();
}

Wir werden dieses Array für jeden Frame der Animation durchgehen und die Pfade einzeln animieren

let fprintTick = getPropertyIncrement(0, 1, TIME_TO_FILL_FPRINT);

function fprintFrame(timestamp) {
  
  // We don't want to paint if less than 1000 / 65 ms elapsed 
  // since the last frame (because there are faster screens 
  // out there and we want the animation to look the same on 
  // all devices). We use 65 instead of 60 because, even on 
  // 60 Hz screens, `requestAnimationFrame` can sometimes be called 
  // a little sooner, which can result in a skipped frame.
  if (timestamp - lastRafCallTimestamp >= 1000 / 65) {
    lastRafCallTimestamp = timestamp;
    curFprintPathsOffset += fprintTick * fprintProgressionDirection;
    offsetAllFprintPaths(curFprintPathsOffset);
  }
  
  // Schedule the next frame if the animation isn't over
  if (curFprintPathsOffset >= -1 && curFprintPathsOffset <= 0) {
    isFprintAnimationInProgress = true;
    window.requestAnimationFrame(fprintFrame);
  }
  
  // The animation is over. We can schedule next animation steps
  else if (curFprintPathsOffset > 0) {
    curFprintPathsOffset = 0;
    offsetAllFprintPaths(curFprintPathsOffset);
    isFprintAnimationInProgress = false;
    isFprintAnimationOver = true;
    // Remove the background with grey paths
    $fprint.addClass('demo__fprint--no-bg');
    // Schedule the next animation step - transforming one of the paths into a string 
    // (this function is not implemented at this step yet, but we'll do that soon)
    startElasticAnimation();
    // Schedule the fingerprint removal (removeFprint function will be implemented in the next section)
    window.requestAnimationFrame(removeFprint);
  }
  // The fingerprint is back to the original state (the user has stopped holding the mouse down)
  else if (curFprintPathsOffset < -1) {
    curFprintPathsOffset = -1;
    offsetAllFprintPaths(curFprintPathsOffset);
    isFprintAnimationInProgress = false;
  }
}

Und wir werden einige Event-Listener an die Demo anhängen

$screen.on('mousedown touchstart', function() {
  fprintProgressionDirection = 1;
  // If the animation is already in progress,
  // we don't schedule the next frame since it's 
  // already scheduled in the `fprintFrame`. Also, 
  // we obviously don't schedule it if the animation 
  // is already over. That's why we have two separate 
  // flags for these conditions.
  if (!isFprintAnimationInProgress && !isFprintAnimationOver)
    window.requestAnimationFrame(fprintFrame);
})

// On `mouseup` / `touchend` we flip the animation direction
$(document).on('mouseup touchend', function() {
  fprintProgressionDirection = -1;
  if (!isFprintAnimationInProgress && !isFprintAnimationOver)
    window.requestAnimationFrame(fprintFrame);
})

...und jetzt sollten wir mit dem ersten Schritt fertig sein! So sieht unsere Arbeit in diesem Schritt aus

Sehen Sie den Pen css-t. Schritt 1 von Kirill Kiyutin (@kiyutink) auf CodePen.

Entfernen des Fingerabdrucks

Dieser Teil ist dem ersten ziemlich ähnlich, nur dass wir jetzt berücksichtigen müssen, dass sich einige Pfade in eine Richtung entfernen und der Rest in die andere. Deshalb haben wir früher den Modifikator --removes-forwards hinzugefügt.

Zuerst haben wir zwei zusätzliche Arrays: eines für die Pfade, die nach vorne entfernt werden, und ein weiteres für die, die nach hinten entfernt werden

const fprintPathsFirstHalf = [];
const fprintPathsSecondHalf = [];

for (let i = 0; i < $(fprintPathSelector).length; i++) {
  // ...
  if (fprintPaths[i].removesForwards)
    fprintPathsSecondHalf.push(fprintPaths[i]);
  else
    fprintPathsFirstHalf.push(fprintPaths[i]);
}

...und wir schreiben eine Funktion, die sie in die richtige Richtung verschiebt

function offsetFprintPathsByHalves(ratio) {    
  fprintPathsFirstHalf.forEach(path => path.offset(ratio));
  fprintPathsSecondHalf.forEach(path => path.offset(-ratio));
}

Wir werden auch eine Funktion benötigen, die die Frames zeichnet

function removeFprintFrame(timestamp) {
  // Drop the frame if we're faster than 65 fps
  if (timestamp - lastRafCallTimestamp >= 1000 / 65) {
    curFprintPathsOffset += fprintTick * fprintProgressionDirection;
    offsetFprintPathsByHalves(curFprintPathsOffset);
    lastRafCallTimestamp = timestamp;
  }
  // Schedule the next frame if the animation isn't over
  if (curFprintPathsOffset >= -1)
    window.requestAnimationFrame(removeFprintFrame);
  else {
    // Due to the floating point errors, the final offset might be 
    // slightly less than -1, so if it exceeds that, we'll just 
    // assign -1 to it and animate one more frame
    curFprintPathsOffset = -1;
    offsetAllFprintPaths(curFprintPathsOffset);
  }
}

function removeFprint() {
  fprintProgressionDirection = -1;
  window.requestAnimationFrame(removeFprintFrame);
}

Jetzt müssen wir nur noch removeFprint aufrufen, wenn wir mit dem Füllen des Fingerabdrucks fertig sind

function fprintFrame(timestamp) {
  // ...
  else if (curFprintPathsOffset > 0) {
    // ...
    window.requestAnimationFrame(removeFprint);
  }
  // ...
}

Schauen wir uns unsere Arbeit jetzt an

Sehen Sie den Pen css-t. Teil 2 von Kirill Kiyutin (@kiyutink) auf CodePen.

Animieren der Pfadenden

Sie sehen, dass einige der Pfade länger sind, als sie ursprünglich waren, da der Fingerabdruck fast entfernt ist. Ich habe sie in separate Pfade verschoben, die im richtigen Moment mit der Animation beginnen. Ich könnte sie in die vorhandenen Pfade integrieren, aber das wäre viel schwieriger und bei 60fps würde es kaum einen Unterschied machen.

Erstellen wir sie

<path class="demo__ending-path demo__ending-path--pinkish" d="M48.4,220c-5.8,4.2-6.9,11.5-7.6,18.1c-0.8,6.7-0.9,14.9-9.9,12.4c-9.1-2.5-14.7-5.4-19.9-13.4c-3.4-5.2-0.4-12.3,2.3-17.2c3.2-5.9,6.8-13,14.5-11.6c3.5,0.6,7.7,3.4,4.5,7.1"/>
<!-- and 5 more paths like this -->

...und wenden einige grundlegende Stile an

&__ending-path {
  fill: none;
  stroke-width: 2.5px;
  stroke-dasharray: 60 1000;
  stroke-dashoffset: 61;
  stroke-linecap: round;
  will-change: stroke-dashoffset, stroke-dasharray, opacity;
  transform: translateZ(0);
  transition: stroke-dashoffset 1s ease, stroke-dasharray 0.5s linear, opacity 0.75s ease;
  
  &--removed {
    stroke-dashoffset: -130;
    stroke-dasharray: 5 1000;
  }
  
  &--transparent {
    opacity: 0;
  }
  
  &--pinkish {
    stroke: $pinkish-color;
  }
  
  &--purplish {
    stroke: $purplish-color;
  }
}

Nun müssen wir den Modifikator --removed hinzufügen, damit diese Pfade im richtigen Moment eingefügt werden

function removeFprint() {
  $endingPaths.addClass('demo__ending-path--removed');
  setTimeout(() => {
    $endingPaths.addClass('demo__ending-path--transparent');
  }, TIME_TO_REMOVE_FPRINT * 0.9);
  // ...
}

Nun nimmt unsere Arbeit wirklich Gestalt an

Sehen Sie den Pen css-t. Teil 3 von Kirill Kiyutin (@kiyutink) auf CodePen.

Morphing des Fingerabdrucks

Okay, diesen Teil fand ich sehr schwer selbst zu machen, aber mit dem morphSVG-Plugin von GSAP ist es sehr einfach zu implementieren.

Lassen Sie uns die unsichtbaren Pfade erstellen (naja, einen Pfad und eine Linie, um genau zu sein 🙂), die die Keyframes für unsere Zeichenkette sein werden

<line id='demo__straight-path' x1="0" y1="151.3" x2="180" y2="151.3"/>
<path class="demo__hidden-path" id='demo__arc-to-top' d="M0,148.4c62.3-13.5,122.3-13.5,180,0"/>

Dann verwenden wir morphSVG, um den Pfad zwischen den Keyframes zu überführen

const $elasticPath = $('#demo__elastic-path');

const ELASTIC_TRANSITION_TIME_TO_STRAIGHT = 250;
const WOBBLE_TIME = 1000;

function startElasticAnimation() {
  $elasticPath.css('stroke-dasharray', 'none');
  const elasticAnimationTimeline = new TimelineLite();
  
  elasticAnimationTimeline
    .to('#demo__elastic-path', ELASTIC_TRANSITION_TIME_TO_STRAIGHT / 1000, {
      delay: TIME_TO_REMOVE_FPRINT / 1000 * 0.7,
      morphSVG: '#demo__arc-to-top'
    })
    .to('#demo__elastic-path', WOBBLE_TIME / 1000, {
      morphSVG: '#demo__straight-path',
      // I played with the easing a bit to get that "vibration" effect
      ease: Elastic.easeOut.config(1, 0.3)
    })
}

Wir rufen diese Funktion in fprintFrame auf, sobald der Fingerabdruck gefüllt ist

function fprintFrame(timestamp) {
  // ...
  else if (curFprintPathsOffset > 0) {
    // ...
    startElasticAnimation();
    // ...
  }
  // ...
}

Das Ergebnis ist dies

Sehen Sie den Pen css-t. Teil 4 von Kirill Kiyutin (@kiyutink) auf CodePen.

Animieren der schwebenden Kugel

Dafür habe ich einfache, direkte CSS-Animationen verwendet. Ich habe die Timing-Funktionen so gewählt, dass sie die Schwerkraft simulieren. Sie können hier oder hier mit den Timing-Funktionen herumspielen.

Lassen Sie uns ein div erstellen

<div class="demo__bullet"></div>

...und ihm einige Stile zuweisen

&__bullet {
  position: absolute;
  width: 4px * $scale;
  height: 4px * $scale;
  background-color: white;
  border-radius: 50%;
  top: 210px * $scale;
  left: 88px * $scale;
  opacity: 0;
  transition: all 0.7s cubic-bezier(0.455, 0.030, 0.515, 0.955);
  will-change: transform, opacity;
  
  // This will be applied after the bullet has descended, to create a transparent "aura" around it
  &--with-aura {
    box-shadow: 0 0 0 3px * $scale rgba(255, 255, 255, 0.3);
  }
  // This will be applied to make the bullet go up
  &--elevated {
    transform: translate3d(0, -250px * $scale, 0);
    opacity: 1;
  }
  // This will be applied to make the bullet go down
  &--descended {
    transform: translate3d(0, 30px * $scale, 0);
    opacity: 1;
    transition: all 0.6s cubic-bezier(0.285, 0.210, 0.605, 0.910);
  }
}

Dann binden wir es zusammen, indem wir Klassen basierend auf den Interaktionen eines Benutzers hinzufügen und entfernen

const DELAY_TO_BULLET_AURA = 300;
const ELEVATION_TIME = 700;
const DELAY_AFTER_ELEVATION = 700;

const $bullet = $('.demo__bullet');

function elevateBullet() {
  $bullet.addClass('demo__bullet--elevated');
}

function descendBullet() {
  $bullet.addClass('demo__bullet--descended').removeClass('demo__bullet--elevated');
  animateBulletAura();
}
  
function animateBulletAura() {
  setTimeout(() => $bullet.addClass('demo__bullet--with-aura'), DELAY_TO_BULLET_AURA);
}

function animateBullet() {
  elevateBullet();
  $screen.removeClass('demo__screen--clickable');
  setTimeout(descendBullet, ELEVATION_TIME + DELAY_AFTER_ELEVATION);
}

Nun müssen wir die Funktion animateBullet aufrufen

function startElasticAnimation() {
  // ...
  animateBullet();
}

Hier sind wir an diesem Punkt

Sehen Sie den Pen css-t. Teil 5 von Kirill Kiyutin (@kiyutink) auf CodePen.

Morphing der Zeichenkette in ein Diagramm

Nun, lassen Sie uns diese Zeichenkette in ein Diagramm verwandeln, in dem die Kugel landen kann. Wir fügen der morphSVG-Animation einen weiteren Keyframe hinzu.

<path class="demo__hidden-path" id='demo__curve' d="M0,140.2c13.1-10.5,34.7-17,48.5-4.1c5.5,5.2,7.6,12.1,9.2,19.2c2.4,10.5,4.3,21,7.2,31.4c2.4,8.6,4.3,19.6,10.4,26.7c4.3,5,17.7,13.4,23.1,4.8c5.9-9.4,6.8-22.5,9.7-33c4.9-17.8,13-14.6,15.7-14.6c1.8,0,9,2.3,15.4,5.4c6.2,3,11.9,7.7,17.9,11.2c7,4.1,16.5,9.2,22.8,6.6"/>

Wir fügen diesen Keyframe wie folgt in unsere Timeline ein

const DELAY_TO_CURVE = 350;
const ELASTIC_TRANSITION_TIME_TO_CURVED = 300;

function startElasticAnimation() {
  // ...
  elasticAnimationTimeline
    // ...
    .to('#demo__elastic-path', ELASTIC_TRANSITION_TIME_TO_CURVED / 1000, {
      delay: DELAY_TO_CURVE / 1000, 
      morphSVG: '#demo__curve'
    })
    // ...
}

Hier ist, was wir bekommen

Sehen Sie den Pen css-t. Teil 6 von Kirill Kiyutin (@kiyutink) auf CodePen.

Explodierende Partikel

Das ist eine lustige Animation. Zuerst erstellen wir ein paar neue divs, die die explodierenden Partikel enthalten

<div class="demo__logo-particles">
    <div class="demo__logo-particle"></div>
    <!-- and several more of these -->
</div>
<div class="demo__money-particles">
    <div class="demo__money-particle"></div>
    <!-- and several more of these -->
</div>

Die beiden Explosionen sind praktisch gleich, mit Ausnahme einiger Parameter. Hier sind SCSS-Mixins nützlich. Wir können die Funktion einmal schreiben und sie auf unsere divs anwenden.

@mixin particlesContainer($top) {
  position: absolute;
  width: 2px * $scale;
  height: 2px * $scale;
  left: 89px * $scale;
  top: $top * $scale;
  // We'll hide the whole container to not show the particles initially
  opacity: 0;

  &--visible {
    opacity: 1;
  }
}

// The $sweep parameter shows how far from the center (horizontally) the initial positions of the particles can be
@mixin particle($sweep, $time) {
  width: 1.5px * $scale;
  height: 1.5px * $scale;
  border-radius: 50%;
  background-color: white;
  opacity: 1;
  transition: all $time ease;
  position: absolute;
  will-change: transform;
  
  // Phones can't handle the particles very well :(
  @media (max-width: 400px) {
    display: none;
  }

  @for $i from 1 through 30 {
    &:nth-child(#{$i}) {
      left: (random($sweep) - $sweep / 2) * $scale + px;
      @if random(100) > 50 {
        background-color: $purplish-color;
      }
      @else {
        background-color: $pinkish-color;
      }
    }
    &--exploded:nth-child(#{$i}) {
      transform: translate3d((random(110) - 55) * $scale + px, random(35) * $scale + px, 0);
      opacity: 0;
    }
  }
}

Beachten Sie den Kommentar im Code, dass die Partikel auf weniger leistungsfähigen Geräten wie Handys nicht besonders gut funktionieren. Vielleicht gibt es hier einen anderen Ansatz, der dies lösen würde, wenn jemand Ideen hat und sich einbringen möchte.

Also, lassen Sie uns die Mixins auf die Elemente anwenden

&__logo-particles {
  @include particlesContainer(15px);
}

&__logo-particle {
  @include particle(50, 1.7s);
}

&__money-particles {
  @include particlesContainer(100px);
}

&__money-particle {
  @include particle(100, 1.5s);
}

Nun fügen wir die Klassen zu den divs zur richtigen Zeit in JavaScript hinzu

const DELAY_TO_ANIMATE_MONEY_PARTICLES = 300;
const DELAY_TO_ANIMATE_LOGO_PARTICLES = 500;

const $moneyParticles = $('.demo__money-particle');
const $moneyParticlesContainer = $('.demo__money-particles');
const $logoParticlesContainer = $('.demo__logo-particles');
const $logoParticles = $('.demo__logo-particle');

function animateMoneyParticles() {
  setTimeout(() => {
    $moneyParticlesContainer.addClass('demo__money-particles--visible')
    $moneyParticles.addClass('demo__money-particle--exploded');
  }, DELAY_TO_ANIMATE_MONEY_PARTICLES);    
}

function animateLogoParticles() {
  setTimeout(() => {
    $logoParticlesContainer.addClass('demo__logo-particles--visible')
    $logoParticles.addClass('demo__logo-particle--exploded');
  }, DELAY_TO_ANIMATE_LOGO_PARTICLES);    
}

function elevateBullet() {
  // ...
  animateMoneyParticles();
  animateLogoParticles();
}

Hier sind wir

Sehen Sie den Pen css-t. Teil 7 von Kirill Kiyutin (@kiyutink) auf CodePen.

Animieren des Kontostands

Jede Ziffer erhält ein paar zufällige Zahlen, durch die wir scrollen werden

<div class="demo__money">
  <div class="demo__money-currency">$</div>
  <!-- every digit will be a div like this one -->
  <div class="demo__money-digit">
    1
    2
    3
    4
    5
    6
    7
    8
    1
  </div>
  // ...
</div>

Wir werden den Ziffern unterschiedliche Übergangszeiten zuweisen, damit die Animationen versetzt sind. Wir können dafür eine SCSS-Schleife verwenden

&__money-digit {
  // ...
  // we start from 2 because the first child is the currency sign :)
  @for $i from 2 through 6 {
    &:nth-child(#{$i}) {
      transition: transform 0.1s * $i + 0.2s ease;
      transition-delay: 0.3s;
      transform: translate3d(0, -26px * $scale * 8, 0);
    }
    
    &--visible:nth-child(#{$i}) {
      transform: none;
    }
  }
}

Alles, was noch zu tun ist, ist, die CSS-Klassen zur richtigen Zeit hinzuzufügen

const $money = $('.demo__money');
const $moneyDigits = $('.demo__money-digit');

function animateMoney() {
  $money.addClass('demo__money--visible');
  $moneyDigits.addClass('demo__money-digit--visible');
}

function descendBullet() {
  // ...
  animateMoney();
  // ...
}

Nun lehnen Sie sich zurück und bestaunen Sie unsere Arbeit

Sehen Sie den Pen css-t. Teil 8 von Kirill Kiyutin (@kiyutink) auf CodePen.

Die restlichen Animationen sind ziemlich einfach und beinhalten leichte CSS-Übergänge, daher werde ich nicht näher darauf eingehen, um es kurz zu halten. Den gesamten endgültigen Code finden Sie in der abgeschlossenen Demo.

Demo ansehen

Einige abschließende Worte

  • In meinen frühen Versuchen habe ich versucht, CSS-Übergänge für die gesamte Animationsarbeit zu verwenden. Ich fand es praktisch unmöglich, den Fortschritt und die Richtung der Animation zu kontrollieren, also habe ich diese Idee schnell aufgegeben und etwa einen Monat gewartet, bevor ich erneut begann. In Wirklichkeit, wenn ich damals gewusst hätte, dass die Web Animations API existiert, hätte ich versucht, sie zu nutzen.
  • Ich habe versucht, die Explosion mit Canvas für bessere Leistung zu realisieren (mit diesem Artikel als Referenz), aber ich fand es schwierig, die Bildrate mit zwei separaten requestAnimationFrame-Ketten zu kontrollieren. Wenn Sie wissen, wie das geht, können Sie es mir vielleicht in den Kommentaren sagen (oder einen Artikel für CSS-Tricks schreiben).
  • Nachdem ich einen ersten funktionierenden Prototypen hatte, war ich mit seiner Leistung sehr unzufrieden. Ich erreichte etwa 40-50fps auf einem PC, ganz zu schweigen von Handys. Ich habe viel Zeit damit verbracht, den Code zu optimieren, und dieser Artikel war sehr hilfreich.
  • Sie sehen, dass das Diagramm einen Gradienten hat. Das habe ich erreicht, indem ich einen Gradienten direkt im SVG-Defs-Block deklariert habe
<defs>
  <linearGradient id="linear" x1="0%" y1="0%" x2="100%" y2="0%">
    <stop offset="0%"   stop-color="#8742cc"/>
    <stop offset="100%" stop-color="#a94a8c"/>
  </linearGradient>
</defs>

...und ihn dann in den CSS-Eigenschaften angewendet habe

fill: url(#linear);
stroke: url(#linear);

Der gesamte Prozess von Anfang bis Ende – vom Entdecken des Dribbble-Shots bis zum Abschluss der Arbeit – dauerte etwa ein Jahr. Ich habe hier und da monatelange Pausen eingelegt, entweder weil ich nicht wusste, wie ich einen bestimmten Aspekt angehen sollte, oder weil ich einfach nicht genug Freizeit hatte, um daran zu arbeiten. Der gesamte Prozess war eine sehr wertvolle Erfahrung und ich habe unterwegs viel Neues gelernt.

Das gesagt, die wichtigste Lektion, die Sie mitnehmen sollten, ist, dass Sie keine Angst haben müssen, eine ehrgeizige Aufgabe anzunehmen oder entmutigt zu sein, wenn Sie sie zunächst nicht angehen können. Das Web ist ein großer Ort und es gibt viel Platz, um die Dinge herauszufinden, während Sie fortfahren.