CSS: Hüpfende Bälle

möglichst realsitisch animiert

Hier also – wie angekündigt – ein weiteres Projekt aus der Kategorie "Funny CSS Stuff": Heute geht es um animierte, halbwegs realistisch hübpfende bunte Bälle mit Schatten:

1. Worum es geht…

… im Schnelldurchlauf in diesem Video:

Erstellung des CSS-Projektes in Zeitraffer,
Musik: Motivational Inspirational Cinematic Trailer von Michael Schuller, Quelle: FMA, Lizenz: CC-by-nd

2. Das HTML: Weniger ist mehr

Wir benötigen nur eine Bühne und Bälle. Die Doppelklassen der Bälle erklärt sich aus dem Wunsch nach aufgeräumtem Code. Die Klasse ball sorgt für das grundsätzliche Design, die Klassen bnrX (Ball Nr. X) für die Unterschiede (Farbe und Timing):

HTML

<div class="ballwrapper">
  <h1>Maus hierhin bewegen oder antippen!</h1>
  <div class="ball bnr1"></div>
  <div class="ball bnr2"></div>
  <div class="ball bnr3"></div>
</div>

3. Die Bühne: Ein Wrapper mit Farbverlauf

Als Bühne bzw. Hintergrund dient also ein einfaches div mit einem Farbverlauf (linear-gradient) als Hintergrund. Das CSS sieht wie folgt aus:

CSS

/** Die Bühne **/
.ballwrapper {
  width:100%;
  max-width:1000px;
  min-width:360px;
  margin:0 auto;
  height:400px;
  background: #ffffff linear-gradient(to bottom, rgba(255,255,255,1) 65%, rgba(0,0,0,0.1) 75%);
  border:1px solid #bf3048;
  overflow:hidden;
}
/** Der Text **/
.ballwrapper h1 {
  font-size: 2em;
  top: 75px;
  position: relative;
  text-align: center;
  padding-bottom: 0;
  font-family: alda;
  color: #aaa;
}

Gerendert sieht dann so aus:

Maus hierhin bewegen oder antippen!

4. Die Kugeln: divs mit Verläufen

Die Kugeln sind ebenfalls divs mit fester Breite und Höhe sowie einem border-radius von 50%. Ihre Räumlichkeit erhalten sie durch einen Farbverlauf. Den background statte ich hierfür immer mit einer individuellen (bnrx-) Grundfarbe aus, welche ich in der allgemeinen .ball-Definition mit einem radial-gradient mit verschobenem Mittelpunkt (at 60% 30%), mit einem Highlight (weiß mit einer Deckkraft von 60%: rgba(255,255,255,0.6);), einem transparenten Bereich (Farbe egal, Deckkraft 0: rgba(255,255,255,0);) und einem Schatten (schwarz mit einer Deckkraft von 60%: rgba(0,0,0,0.6);) "überlagere": So muss man bei den Bällen über die Klassen .bnr1, .bnr2, .bnr3 nur die Hauptfarbe ändern, auf die dann das Highlight und der Schatten „angewendet“ wird. Der mittlere transparente Bereich ist dazu da, diese Hauptfarbe auch zur Geltung zu bringen. Die eigentliche Farbe lässt sich so super einfach je Ball ändern, ohne jedesmal den Verlauf wieder anpacken zu müssen:

CSS

/** Die Bälle **/
.ball {
  width:100px;
  height:100px;
  border-radius:50%;
  background-image: radial-gradient(at 60% 30%, rgba(255,255,255,0.6) 0%,rgba(255,255,255,0) 40%,rgba(0,0,0,0.6) 80%);
  margin:120px 10px 0;
  float:left;
}
/** Individuelles Ball-Styling, Positionierung **/
.bnr1 {background-color: #bf3048;margin-left: calc(50% - 180px);}
.bnr2 {background-color: #30a7bf;}
.bnr3 {background-color: #86bf30;}

Ball Nr. 1 bekommt darüber hinaus einen margin-left von calc(50% - 180px);. Warum? Weil ein Ball 100px breit ist und auf beiden Seiten 10px Abstand hat. Also sind alle drei Bälle zusammen 3 * 120px = 360px breit. Damit sie sich mittig auf der Bühne befinden, soll der linke Abstand 50% der Bühne minus die Hälfte der Breite aller drei Bälle (=180px) sein. Aus der Gesamtbreite aller drei Kugeln erklärt sich übrigens auch die min-width von 360px des .ballwrappers 🙂

Ergebnis:

Maus hierhin bewegen oder antippen!

 
 
 

So weit, so hübsch. Jetzt sollen sie sich aber auch noch bewegen:

5. Die Animation: :hover-Auslöser, @keyframes, timing, delay

Die Animation wird an einen :hover-, wahlweise auch :active-Auslöser gekoppelt; in Form einer animation namens bounce, die eine Sekunde (1s) dauern soll und linear unendlich (infinite) ablaufen soll. Die "Physik", also das typische Fallen, Tupfen, Abprallen machen wir später 🙂

CSS

/** Basisanimation der Bälle **/
.ballwrapper:hover .ball,
.ballwrapper:active .ball {
  animation: bounce 1s linear infinite;
}

Die eigentliche Animation wird mittels @keyframes definiert. Diese Schlüsselbilder werden innerhalb des Animationsverlaufes (1s, s. Längenangabe der animation:bounce) mittels Prozentwerten definiert. Wir benötigen ein unteres (Anfang und Ende der Animation: 0%, 100%) und ein oberes (Mitte der Animation: 50%) Schlüsselbild, denen wir über transform eine jeweils unterschiedliche Position mitgeben. Das Schlüsselbild in der Mitte soll um 100% (bei einer Höhe von 100px der .ball-Klasse sind das 100px) nach oben (translateX = horizontal, translateY = vertikal, positive Werte verschieben nach unten bzw. rechts, negative Werte nach oben bzw. links) verschoben werden:

CSS

/** Schlüsselbilder der Ball-Animation **/
@keyframes bounce {
   0%, 100% {transform: translateY(0%);}
  50%       {transform: translateY(-100%);}
}

Also: Bitte "hovern":

Maus hierhin bewegen oder antippen!

 
 
 

Das sieht jetzt noch nicht soooo realistisch aus. Um eine realsitische Physik darzustellen, kommen Bezier-Kurven zum Einsatz. Die habe ich schon mal im Zusammenhang mit CSS-Transitions hier genutzt. Die Seite http://cubic-bezier.com/ ist immer noch mein Favorit, um ein spezielles Timing für CSS-Transitions oder CSS-Animationen zu basteln 🙂

Links die Bewegung nach oben: Erst viel Bewegung in wenig Zeit, dann langsamer werdend; rechts die Bewegung nach unten: Fällt langsam und dann immer schneller.

Die Bewegung nach oben muss schnell starten, schließlich soll der Ball vom Boden abprallen und sich nach oben bis zum Stillstand verlangsamen. Die Bewegung nach unten soll durch die simulierte Gravitation dagegen vom Scheitelpunkt bis unten immer schneller werden, also in etwa so:

Der Code der Bezier-Handles (nach unten und nach oben) wird mittels animation-timing-function in die Keyframes der Animation hineingeschrieben. Bei der Gelegenheit füge ich auch gleich noch eine horizontale Skalierung (scaleX) von 105% (1.05) für das untere und eine von 95% (0.95) für das obere Schlüsselbild ein, um die Verformung beim Aufpraller zu simulieren:

CSS

/** Schlüsselbilder der Ball-Animation **/
@keyframes bounce {
  0%, 100% {
    transform: translateY(0%) scaleX(1.05);
    animation-timing-function:cubic-bezier(.05,.6,.45,.95);
  } 50% {
    transform: translateY(-100%) scaleX(0.95);
    animation-timing-function:cubic-bezier(.5,0,.9,.2);
  }
}

Jetzt passen wir auch die Animation der einzelnen Bälle via animation-delay an …

CSS

/** Verzögerung der Ball-Animation 2 und 3 **/
.ballwrapper:hover .bnr2 {animation-delay: 0.2s;}
.ballwrapper:hover .bnr3 {animation-delay: 0.4s;}

… und dann sieht die Bewegung schon gleich viel realistischer aus:

Maus hierhin bewegen oder antippen!

 
 
 

Jaw. Das ist cool. Manchmal muss ich mich auch einfach mal loben 🙂

Früher hätten wir da mit Photoshop oder im Gif-Maker lange dran gesessen, um so eine Animation als gif zu basteln – oder noch schlimmer – als Flash-Movie. Jetzt reichen ca. 100 Zeilen HTML/CSS, die sogar ich zusammenbasteln kann 🙂

5. Die Schatten

Um dem Ganzen jetzt noch die Krone aufzusetzen, bekommen die Bälle jeder noch einen Schatten. Diese sollen

  • passend zur vermeintlichen Lichtquelle rechts oben (Zur Erinnerung: Verschobener Mittelpunkt des radial-gradient-Hintergrundes der .ball-Klasse!) nach links unten fallen,
  • bei der Ballbewegung nicht nach unten (ist ja kein Spiegelbild) sondern nach links über den vermeintlichen Boden wandern und
  • heller und unschärfer werden, je höher der Ball ist!

Klingt frickelig? Ist es auch ein wenig, aber man kann den bisherigen Code recyclen, wenn man ihn ein wenig ändert. Schließlich haben wir schon Keyframes und Timing. Der rest sollte nicht sooo schwierig sein… Im HTML fügen wir drei weitere divs mit den Klassen shadow und snrX (Schatten Nr. X) ein:

HTML

<div class="ballwrapper">
  <h1>Maus hierhin bewegen<br />oder antippen!</h1>
  <div class="ball bnr1"></div>
  <div class="ball bnr2"></div>
  <div class="ball bnr3"></div>
  <div class="shadow snr1"></div>
  <div class="shadow snr2"></div>
  <div class="shadow snr3"></div>
</div>

Die Schatten werden via CSS alle erst einmal folgendermaßen gestyled: Platte Ellipse (120 * 30px), 30% Schwarz (rgba(0,0,0,0.3);), 3px Weichzeichner via filter:blur(3px);. Die Positionierung findet wie bei .ball wieder über den margin statt, wobei hier beachtet werden muss, dass die Schatten mit 120px breiter sind als die Bälle, also keinen horizontalen Abstand benötigen und 20px nach oben "unter" die Bälle geschoben werden müssen (daher: -20px 0 0). Der erste Schatten .snr1 bekommt wieder den berechneten Abstand via calc, diesmal allerdings (50% - 210px), um die Schatten passend zur angenommenen Lichtquelle nach links zu verschieben.

CSS

/** Basis-Schatten **/
.shadow {
  width:120px;
  height:30px;
  background-color:rgba(0,0,0,0.3);
  border-radius:50%;
  position:relative;
  z-index: 5;
  filter:blur(3px);
  margin:-20px 0 0;
  float:left;
}
/** Positionierung der Schatten **/
.snr1 {margin-left:calc(50% - 210px);}

Damit die Schatten räumlich "unter" (im stacking index richtigerweise "hinter") den Bällen dargestellt werden, müssen die Bälle ebenfalls einen z-index bekommen. Und der z-index wiederum funktioniert ja nur bei positionierten Objekten, daher benötigen sowohl .shadow (s.o.) als auch .ball (s.u.) eine position:relative;:

CSS

.ball{
  ...
  position:relative;
}

Das Ganze sieht dann erst einmal so aus:

Maus hierhin bewegen oder antippen!

 
 
 
 
 
 

Jetzt müssen die Schatten nur noch animiert werden. Das mache ich analog zur Animation der Bälle, nun aber mit einer Bewegung auf der X-Achse (translateX) und Manipulationen der Deckkraft (opacity) und einem Weichzeichnungs-Filter (blur):

CSS

/** Basis-Schatten-Animation **/
.ballwrapper:hover .shadow {
  animation: shadow 1s linear infinite;
}
/** Schlüsselbilder der Schatten-Animation **/
@keyframes shadow {
  0%, 100% {
    transform:translateX(-10%) scaleX(1);
    animation-timing-function: cubic-bezier(.05,.6,.45,.95);
    filter:blur(3px); opacity:1;
  } 50% {
    transform:translateX(-30%) scaleX(1.5);
    animation-timing-function:cubic-bezier(.5,0,.9,.2);
    filter:blur(7px);
    opacity:0.4;
  }
}
/** Timing der Schatten-Animation 2 und 3 **/
.ballwrapper:hover .snr2 { animation-delay: 0.2s; }
.ballwrapper:hover .snr3 { animation-delay: 0.4s; }

So sollte es funktionieren. Hier bitte testen:

Maus hierhin bewegen oder antippen!

 
 
 
 
 
 

Passt, oder? Noch ein wenig Futter für die Kritiker unter Euch:

  • Ja, die Lichtreflektion könnte sich bei der Ballbewegung ebenfalls bewegen. Wie ich den Mittelpunkt eines radial-gradient animiere, weiß ich aber nicht. Die Lösung bitte gerne in die Kommentare.
  • Ja, die Verformung der Bälle dürfte nicht kontinuierlich über die Flugbahn geschehen, sondern nur im Moment des Aufpralls unten. Dafür wäre eine eigenständige Animation notwendig, da diese eine andere animation-timing-function benötigen würde. Die Lösung bitte gerne in die Kommentare.
  • Ja, das ist kein Realfilm, sondern eine CSS-Animation, das muss ein wenig unrealistisch aussehen…

Sinnvolle Anwendungsbeispiele jenseits der Spielerei sind jetzt eher schwierig 🙂 … aber eine Ladeseite oder eine pfiffige 404 könnte ich mir vorstellen.

Hier noch einmal das gesamte HTML und CSS alles in einer Datei zum Download.

Viel Spaß beim basteln!

"CSS: Hüpfende Bälle" von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht.
Darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert