Wiederholbare, gestaffelte Animationen auf drei Arten: Sass, GSAP und Web Animations API

Avatar of Opher Vishnia
Opher Vishnia am

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

Gestaffelte Animationen, auch bekannt als „Follow Through“ oder „Overlapping Action“, sind eines der zwölf Disney-Prinzipien der Animation, wie sie von Ollie Johnston und Frank Thomas in ihrem Buch „The Illusion of Life“ von 1981 definiert wurden. Im Kern geht es bei diesem Konzept darum, Objekte in verzögerter Abfolge zu animieren, um flüssige Bewegungen zu erzeugen.

Die Technik beschränkt sich jedoch nicht nur auf niedliche Charakteranimationen. Das Motion-Design-Aspekt einer digitalen Benutzeroberfläche hat erhebliche Auswirkungen auf UX, Benutzerwahrnehmung und das „Gefühl“. Google erwähnt gestaffelte Animationen sogar auf seiner Seite Motion Choreography als Teil des Material Design-Leitfadens.

Obwohl das Thema Motion Design wirklich riesig ist, wende ich oft kleine Teile davon auch bei den kleinsten Projekten an. Während des Designprozesses der interaktiven Coke-Anzeige auf Eko wurde ich beauftragt, einige Animationen zu erstellen, die beim Laden der interaktiven Videos gezeigt werden sollten, und so entstand dieses Mockup.

Auf den ersten Blick scheint diese Animation in CSS trivial zu implementieren zu sein, aber das ist nicht der Fall! Während es mit GSAP und der brandneuen Web Animations API einfacher sein mag, erfordert die Umsetzung mit CSS einige Tricks, die ich in diesem Beitrag erklären werde. Warum überhaupt CSS verwenden? In diesem Fall – da die Animation laufen sollte, während der Benutzer auf das Laden von Assets wartet – machte es wenig Sinn, eine Animationsbibliothek zu laden, nur um einen Lade-Spinner anzuzeigen.

Zuerst ein wenig zur Anatomie der Animation.

Es gibt vier Kreise, die absolut innerhalb eines Containers mit `overflow: hidden` positioniert sind, um die Ränder der beiden äußersten Kreise zu rahmen und zu beschneiden. Warum vier und nicht drei? Weil der erste außer Bild ist und darauf wartet, von links einzutreten, und der letzte von rechts aus dem Bild austritt. Die beiden anderen sind immer im Bild. So sieht der Endzustand der Animationsiteration genau wie sein Anfangszustand aus. Kreis 1 nimmt den Platz von Kreis 2 ein, Kreis 2 den von Kreis 3 und so weiter.

Hier ist das grundlegende HTML

<div id="container">
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</div>

Und das begleitende CSS

#container {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 160px;
  height: 40px;
  display: block;
  overflow: hidden;
}
span {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #4df5c4;
  display: inline-block;
  position: absolute; 
  transform: translateX(0px);
}

Versuchen wir das mit einer einfachen Animation für jeden Kreis, die X von 0 auf 60 Pixel verschiebt

Siehe Pen dot loader – no stagger von Opher Vishnia (@OpherV) auf CodePen.

Sieht irgendwie seltsam und roboterhaft aus, oder? Das liegt daran, dass wir eine wichtige Komponente vermissen: gestaffelte Animationen. Das heißt, die Animation jedes Kreises muss ein wenig nach seinem Vorgänger beginnen. „Kein Problem!“, denken Sie sich vielleicht, „wir verwenden die Eigenschaft `animation-delay`“. „Wir geben dem 4. Kreis einen Wert von 0s, dem 3. von 0,15s und so weiter“. Okay, versuchen wir das.

Siehe Pen dot loader – broken von Opher Vishnia (@OpherV) auf CodePen.

Hmm… Was ist gerade passiert? Die Eigenschaft `animation-delay` beeinflusst nur die anfängliche Verzögerung, bevor die Animation startet. Sie fügt keine zusätzlichen Verzögerungen zwischen jeder Iteration hinzu, so dass die Animation aus dem Takt gerät, wie im folgenden Diagramm

Mathematik zur Rettung

Um dies zu überwinden, habe ich die Verzögerung in die Animation eingebaut. CSS-Keyframe-Animationen werden in Prozent angegeben, und mit etwas Berechnung können Sie diese verwenden, um zu definieren, wie viel Verzögerung die Animation enthalten soll. Wenn Sie zum Beispiel eine `animation-duration` von 1s festlegen und Ihren Start-Keyframe bei 0%, die gleichen Werte bei 20%, Ihr Ende bei 80% und die gleichen Endwerte bei 100% angeben, wartet Ihre Animation 0,2 Sekunden, läuft 0,6 Sekunden und wartet dann weitere 0,2 Sekunden.

In meinem Fall wollte ich, dass jeder Kreis mit einer Stagger-Zeit von 0,15 Sekunden wartet, bevor er die eigentliche Animation durchführt, die 0,5 Sekunden dauert, und der gesamte Prozess 1 Sekunde dauert. Das bedeutet, dass die Animation des 4. Kreises 0 Sekunden wartet, dann 0,5 Sekunden animiert und weitere 0,5 Sekunden wartet. Der zweite Kreis wartet 0,15 Sekunden, dann 0,5 Sekunden animiert und 0,35 Sekunden wartet und so weiter.

Um dies zu erreichen, benötigen Sie vier Keyframes (oder drei Keyframe-Paare): 1 und 2 sorgen für die Stagger-Wartezeit, 2 und 3 für die eigentliche Animationszeit, während 3 und 4 für die End-Wartezeit sorgen. Der „Trick“ besteht darin, zu verstehen, wie die erforderlichen Zeiten in Keyframe-Prozente umgewandelt werden, aber das ist eine relativ einfache Berechnung. Zum Beispiel muss der 2. Kreis 0,15 * 2 = 0,3 Sekunden warten und dann 0,5 Sekunden animieren. Ich weiß, dass die Gesamtzeit für die Animation eine Sekunde beträgt, daher werden die Keyframe-Prozente wie folgt berechnet:

0s = 0%
0.3s = 0.3 / 1s * 100 =  30%
0.8s = (0.3 + 0.5) / 1s * 100 = 80%
1s = 100%

Das Endergebnis sieht etwa so aus

Da die gesamte Animation, einschließlich Stagger-Zeit und Wartezeit, in den CSS-Keyframes eingebaut ist und genau eine Sekunde dauert, gerät die Animation nicht aus dem Takt.

Glücklicherweise erlaubt uns Sass, diesen Prozess mit einer einfachen for-Schleife und etwas Inline-Mathematik zu automatisieren, was letztendlich zu einer Reihe von Keyframe-Animationen kompiliert wird. So können Sie die Timing-Variablen manipulieren, um zu experimentieren und zu testen, was für Ihre Animation am besten funktioniert.

@mixin createCircleAnimation($i, $animTime, $totalTime, $delay) {      
  @include keyframes(circle#{$i}) {
    0% {              
      @include transform(translateX(0));            
    }
    #{($i * $delay)/$totalTime * 100}% {     
      @include transform(translateX(0));            
    }          
    #{($i * $delay + $animTime)/$totalTime * 100}% {     
      @include transform(translateX(60px));            
    }          
    100% {
      @include transform(translateX(60px));             
    }
  }      
}

$animTime: 0.5s;
$totalTime: 1s;
$staggerTime: 0.15s;

@for $i from 0 through 3 {
  @include createCircleAnimation($i, $animTime, $totalTime, $staggerTime); 
  span:nth-child(#{($i + 1)}) {
    animation: circle#{(3 - $i)} $totalTime infinite;
    left: #{$i * 60 - 60 }px;
  }
}

Und voilà – hier ist das Endergebnis

<

p data-height="450" data-theme-id="1"="" data-slug-hash="bEydYo" data-default-tab="result" data-user="OpherV" data-embed-version="2" data-pen-title="dot loading animation – SASS stagger" class="codepen">Siehe Pen dot loading animation – SASS stagger von Opher Vishnia (@OpherV) auf CodePen.

Es gibt zwei Hauptnachteile dieser Methode

Erstens müssen Sie sicherstellen, dass die definierte Stagger-Zeit/Animationszeit nicht zu lang ist, so dass sie die gesamte Animationszeit überlappt, sonst brechen die Berechnungen (und die Animation).

Zweitens generiert diese Methode eine Menge CSS-Code, insbesondere wenn Sie Sass verwenden, um alle Präfixe für Browserkompatibilität auszugeben. In meinem Beispiel hatte ich nur vier Elemente zu animieren, aber wenn Sie mehr Elemente haben, ist der generierte Code möglicherweise den Aufwand nicht wert, und Sie möchten wahrscheinlich bei JS-basierten Animationsbibliotheken wie GSAP bleiben. Dennoch ist es ziemlich cool, dies komplett in CSS zu machen.

Das Leben einfacher machen

Um die Ausführlichkeit der Sass-Lösung zu kontrastieren, möchte ich Ihnen zeigen, wie dasselbe einfach mit der Timeline von GSAP und der Funktion `staggerTo` erreicht werden kann.

Siehe Pen dot loading animation – GSAP von Opher Vishnia (@OpherV) auf CodePen.

Hier gibt es zwei interessante Punkte. Erstens wird der letzte Parameter von `staggerTo`, der die Wartezeit zwischen der Animation von Elementen definiert, auf einen negativen Wert (-0.15) gesetzt. Dies ermöglicht es den Elementen, sich in umgekehrter Reihenfolge zu staffeln (Kreis 4–3–2–1 statt 1–2–3–4). Cool, oder?

Zweitens, sehen Sie den Teil mit `tl.set({}, {}, "1");`? Was hat es mit dieser seltsamen Syntax auf sich? Das ist ein cleverer Trick, um die Wartezeit am Ende jeder Kreisanimation zu implementieren. Indem Sie ein leeres Objekt zu einem leeren Objekt zur Zeit 1 setzen, wiederholt sich die Timeline-Animation nun nach der 1-Sekunden-Marke, anstatt nachdem die Kreisanimation beendet war.

Blick in die Zukunft

Die Web Animations API ist das neue und aufregende Kid auf dem Block, aber außerhalb des Rahmens dieses Artikels. Ich konnte es mir jedoch nicht verkneifen, Ihnen eine Beispielimplementierung zu geben, die die gleiche Mathematik wie die CSS-Implementierung verwendet.

Siehe Pen dot loading animation – WAAPI von Opher Vishnia (@OpherV) auf CodePen.

War das hilfreich? Haben Sie mit dieser Technik einige flüssige Animationen erstellt? Lassen Sie es mich wissen!