b.olzplatz für e.learning, me.diensachen und o.nlinekram

Hüpfende Bälle mit CSS

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:

Klicke auf den Button "Inhalt laden", um den externen Inhalt von YouTube nachzuladen. Damit stimmst Du den Datenschutzhinweisen von YouTube zu. Diese findest Du in meiner Datenschutzerklärung im Abschnitt 2.5 verlinkt.
Inhalt laden

PHA+PGlmcmFtZSBzcmM9Imh0dHBzOi8vd3d3LnlvdXR1YmUtbm9jb29raWUuY29tL2VtYmVkL05Qdy1oZFhYUzlVP3JlbD0wIiB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIgZnJhbWVib3JkZXI9IjAiIGFsbG93ZnVsbHNjcmVlbj0iYWxsb3dmdWxsc2NyZWVuIj48c3BhbiBzdHlsZT0iZGlzcGxheTogaW5saW5lLWJsb2NrOyB3aWR0aDogMHB4OyBvdmVyZmxvdzogaGlkZGVuOyBsaW5lLWhlaWdodDogMDsiIGRhdGEtbWNlLXR5cGU9ImJvb2ttYXJrIiBjbGFzcz0ibWNlX1NFTFJFU19zdGFydCI+77u/PC9zcGFuPjxzcGFuIHN0eWxlPSJkaXNwbGF5OiBpbmxpbmUtYmxvY2s7IHdpZHRoOiAwcHg7IG92ZXJmbG93OiBoaWRkZW47IGxpbmUtaGVpZ2h0OiAwOyIgZGF0YS1tY2UtdHlwZT0iYm9va21hcmsiIGNsYXNzPSJtY2VfU0VMUkVTX3N0YXJ0Ij7vu788L3NwYW4+PC9pZnJhbWU+PC9wPg==

Los gehts:

1. 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):

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

2. 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:

/** Die Bühne **/
.ballwrapper {
  width:calc(100% - 4px);
  min-width:360px;
  height:400px;
  background: #ffffff linear-gradient(to bottom, rgba(255,255,255,1) 65%, rgba(0,0,0,0.1) 75%);
  border:1px solid #005566;
  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!

 

3. 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:

/** 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: #005566;margin-left: calc(50% - 180px);}
.bnr2 {background-color: #ffa500;}
.bnr3 {background-color: #aa00cc;}

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:

4. 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 🙂

/** 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:

/** 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 🙂

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:

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.

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:

/** 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 ...

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

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:

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

/** 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;:

.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):

/** 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:

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!

Browser-Kompatibilität

Kritisch könnten hier CSS3-Funktionen sein: Verläufe, 2D Transformationen, Animationen und Filter-Effekte.

Firefox gradient, transform, animation ab v.16 ohne Prefix,
CSS filter ab v.35 ohne Prefix
Chrome gradient ab v. 26,
transform ab v.36,
animation ab v.43,
filter ab v.53 ohne Prefix
Opera gradient ab v. 12.1,
transform ab v.23,
animation ab v.30,
filter ab v.40 ohne Prefix
Safari gradient ab v. 6.1,
transform & animation ab v.9,
filter ab v.9.1 ohne Prefix
Edge gradient, transform & animation ab v.12,
filter ab v.13 ohne Prefix
Internet Explorer gradient, transform ab v.11,
animation ab v.10 ohne Prefix
CSS-Filter werden nicht unterstützt.
Mobile Safari gradient ab iOS 7.1,
transform & animation ab v.9.2,
filter ab v.9.3 ohne Prefix
Android Browser Android 4.4 benötigt das -webkit-Prefix,
danach gehen gradient, transform, animation & filter ohne Prefix

vgl. caniuse.com & shoudiprefix.com.

Schreibe einen Kommentar

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

Zustimmung zur Datenverarbeitung

Einstellungen zu Cookies

Bitte treffe eine Auswahl. Weitere Informationen zu den Auswirkungen Deiner Auswahl findest Du unter Hilfe, in der Datenschutzerklärung und im Impressum.

Treffe bitte eine Auswahl um fortzufahren.

Deine Auswahl wurde gespeichert!

Weitere Informationen

Erläuterung

Um fortfahren zu können, musst Du eine Cookie-Auswahl treffen. Bitte wähle zwischen

  • Alle Cookies zulassen:
    Alle Cookies dieser Seite ("First-Party-Cookies") sowie z.B. Tracking- und Analyse-Cookies, auch von Drittanbietern ("Third-Party-Cookies") sind zugelassen und können auf Deinem Endgerät gespeichert werden.
  • Nur absolut notwendige Cookies zulassen:
    Es werden keine Cookies gesetzt, es sei denn, es handelt sich um technisch notwendige Cookies.

Details zu Cookies und die Option, Deine Einstellungen nachträglich zu ändern gibt es in der Datenschutzerklärung. Die Anbieterkennzeichnung findest Du im Impressum

Zurück zur Auswahl