
Grundlagen - was sind Keyframe-Animationen
Keyframe-Animationen bieten die Möglichkeit, die Darstellung von HTML-Elementen über eine Zeitspanne hinweg zu verändern. Es lassen sich beliebig viele Zwischenstufen der Darstellung (Keyframes) definieren, der Browser berechnet die Übergänge dazwischen - es entsteht Bewegung.
Wir brauchen also zweierlei Dinge:
- Die Definition der Darstellung eines Elements zu verschiedenen Zeitpunkten
- Die Zuweisung der Definition (inkl. Angaben zum Ablauf) zu einem Element
Keyframe-Animationen definieren
Im einfachsten Fall wird ein Start- und ein Endzustand definiert, es können aber auch beliebig viele Zwischenstufen (Keyframes) eingefügt werden. Für jeden Keyframe kann man beliebigen CSS-Eigenschaften (diese sollten natürlich animierbar sein) Werte zuweisen. Allgemein ausgedrückt sieht eine Animation wie folgt aus:
@keyframes nameDerAnimation { x% { Eigenschaft01: Wert; Eigenschaft02: Wert; } y% { Eigenschaft01: Wert; Eigenschaft02: Wert; } z% { Eigenschaft01: Wert; Eigenschaft02: Wert; } }
Derzeit werden Vendor-Präfixe für webkit-basierte Browser noch benötigt, sind hier zur besseren Lesbarkeit jedoch nicht aufgeführt.
Zuweisung einer Animation
Eine Animation lässt sich über folgende CSS-Eigenschaften steuern
animation-name
animation-duration
animation-timing-function
animation-iteration-count
animation-direction
animation-play-state
animation-delay
animation-fill-mode
Essentiell sind der Name und die Dauer der Animation. In Kurzschreibweise lassen sich diese Eigenschaften unter animation
zusammenfassen. Die Zuweisung einer Animation zu einem Element E sieht also mindestens wie folgt aus:
E { animation: name duration; }
Einen Überblick über die verschiedenen Eigenschaften und mögliche Werte findet sich unter http://www.w3.org/TR/css3-animations/. Auch hier werden die Vendor-Präfixe für die Unterstützung der Webkit-Browser noch benötigt.
Slider - Grundfunktion
Nehmen wir uns den angestrebten Slider vor. Dieser besteht in Sachen HTML, wie in Mainova AG beschrieben, aus einer unsortierten Liste für die Slides und Radiobuttons sowie zugehörige Labels zur Steuerung. Der prinzipielle Aufbau des Codes, angereichert mit ein paar zusätzlichen Klassen, die im weiteren Verlauf das Leben leichter machen:
Die einzelnen Slides sind gefloatet, die Position der Liste soll sich je Schritt (Keyframe) um die Breite eines Listenpunktes im Sichtfenster weiterschieben. In der Annahme, dass der Slider aus vier Slides besteht, hat jeder Slide genau 25% der gesamten Zeit der Animation zur Verfügung. Diese Zeit teilt sich dann nochmal in die Standzeit (hier 15%) und die Animationszeit (hier 10%) eines Slides. Die Notation der Animation sieht demnach wie folgt aus:
@keyframes slider { 0%, 15% {left: 0;} 25%, 40% {left: -100%;} 50%, 65% {left: -200%;} 75%, 90% {left: -300%;} 100% {left: -400%;} } .slideList { animation-name: slider; animation-duration: 20s; animation-delay: 2s; animation-iteration-count: infinite; }
Die Angaben kann man natürlich in Kurzschreibweise notieren (s.u.), sind hier jedoch zur besseren Lesbarkeit ausgeschrieben. Notwendige Eigenschaften sind in diesem Fall der Name der Animation, die Dauer und die Wiederholung. Die Verzögerung wird nötig, da bei manueller Steuerung der Slider langsam verschoben wird, die Animation also erst starten darf, wenn die Verschiebung abgeschlossen ist.
Bauen wir die Animation in dieser Form, wird, wie nicht anders zu erwarten, im letzten Schritt der Slider komplett ins Nirwana (-400%) geschoben, es bleibt also nur noch Luft im Sichtfenster übrig. Wir brauchen demzufolge noch eine Möglichkeit, statt dieser Luft den ersten Slide noch einmal zu sehen, bevor die Animation wieder von vorne startet.
Die Luft ist raus
Die Idee ist eigentlich ganz simpel. Wir definieren für den ersten Slide eine weitere Keyframe-Animation, die diesen unmittelbar bevor der letzte Verschub des Sliders stattfindet (bei 90%), nahtlos an seinem Ende anfügt. Danach starten beide Animationen wieder von vorne und es entsteht der Eindruck eines Endlosbandes. Die Stylesheet-Anweisungen dazu:
@keyframes slide01ToEndStart01 { 89.99% {left: 0;} 90%, 100% {left: 100%;} } .slide { position: relative; } .slide01 { animation: slide01ToEndStart01 20s 2s infinite; }
Damit haben wir einen voll funktionstüchtigen Slider, allerdings noch ohne Steuerelemente. Diese kommen im nächsten Schritt.
Steuerungsmöglichkeit für den Benutzer
Die Steuerelemente funktionieren vom Prinzip identisch zu dem in Mainova AG beschriebenen Szenario über die Pseudoklasse :checked
(Checkbox-Hack). Diese Variante läuft auch hier prima, allerdings stolpern wir über das Problem, dass die einmal zugewiesene Animation ohne Unterlass weiterläuft. D.h. bei Klick auf einen Radiobutton rutscht der Slider zwar kurz zur passenden Position, aber dann greift direkt wieder die bei Seitenaufruf gestartete Animation und es wird zum nächsten Keyframe in ihrem Ablauf gesprungen. Zu verhindern wäre dies, wenn nach jeder Wahl eines Radiobuttons (also dem Sprung zu einem bestimmten Slide) die Animation bei eben diesem Slide wieder von vorne losläuft. Da es keine Möglichkeit gibt, einen anderen Startpunkt als 0 für eine Animation zu definieren, bleibt nur übrig, jeweils eine separate Keyframe-Animation zu erstellen und diese in Abhängigkeit des Status der Radiobuttons zuzuweisen. Das könnte dann wie folgt aussehen:
@keyframes slidesStart01 { 0%, 15% {left: 0;} 25%, 40% {left: -100%;} 50%, 65% {left: -200%;} 75%, 90% {left: -300%;} 100% {left: -400%;} } @keyframes slidesStart02 { 90.01% {left: 0;} 0%, 15%, 100% {left: -100%;} 25%, 40% {left: -200%;} 50%, 65% {left: -300%;} 75%, 90% {left: -400%;} } @keyframes slidesStart03 { 65.01% {left: 0;} 0%, 15%, 100% {left: -200%;} 25%, 40% {left: -300%;} 50%, 65% {left: -400%;} 75%, 90% {left: -100%;} } @keyframes slidesStart04 { 40.01% {left: 0;} 0%, 15%, 100% {left: -300%;} 25%, 40% {left: -400%;} 50%, 65% {left: -100%;} 75%, 90% {left: -200%;} } .slideList{ animation: 20s 2s infinite; transition: left 2s; } #slide01:checked ~ .slideList { animation-name: slidesStart01; left: 0; } #slide02:checked ~ .slideList { animation-name: slidesStart02; left: -100%; } #slide03:checked ~ .slideList { animation-name: slidesStart03; left: -200%; } #slide04:checked ~ .slideList { animation-name: slidesStart04; left: -300%; }
Soweit, so gut - aber drei Problemstellen gibt es noch. Erstens muss der erste Slide passend zur jeweils aktiven Animation des Sliders verschoben werden, zweitens lässt sich nicht noch einmal zum aktuell ausgewählten Slide springen und drittens sollten sich die Kontrollelemente natürlich auch dem Status der Animation anpassen.
Position des ersten Slides
Da die Animation jeweils bei einem anderen Slide startet, müssen wir für den ersten Slide weitere Keyframe-Animationen anlegen, damit er zum jeweils passenden Zeitpunkt verschoben wird.
@keyframes slide01ToEndStart01 { 89.99% {left: 0;} 90%, 100% {left: 100%;} } @keyframes slide01ToEndStart02 { 49.99%, 90.01% {left: 0;} 50%, 90% {left: 100%;} } @keyframes slide01ToEndStart03 { 24.99%, 65.01% {left: 0;} 25%, 65% {left: 100%;} } @keyframes slide01ToEndStart04 { 40.01% {left: 0;} 0%, 40% {left: 100%;} } #slide01:checked ~ .slideList .slide01 { animation-name: slide01ToEndStart01; } #slide02:checked ~ .slideList .slide01 { animation-name: slide01ToEndStart02; } #slide03:checked ~ .slideList .slide01 { animation-name: slide01ToEndStart03; } #slide04:checked ~ .slideList .slide01 { animation-name: slide01ToEndStart04; }
Doch damit nicht genug. Es kann passieren, das wir in dem Moment manuell zu einem anderen Slide springen, in dem der erste Slide schon durch die Animation verschoben wurde. Die Position muss also bei Klick auf einen Steuerelement wieder auf Null gesetzt werden. Da der gesamte Slider jedoch langsam verschoben wird (Transition), darf der erste Slide nicht sofort zurückgesetzt werden, sondern mit einer entsprechenden Verzögerung. Diese wird per transition-delay
gesteuert:
.slide { transition: left 0s .5s; } #slide04:checked ~ .slideList .slide { transition-delay: 2s; }
Volle Kontrolle
Der aktuell markierte Slide kann bis jetzt nicht wieder angewählt werden. Ist auch logisch, da die Animation nicht den Zustand der Radiobuttons steuert, der einmal gewählte also so lange ausgewählt bleibt, bis man einen anderen anklickt. Ein erneuter Klick auf einen ausgewählten Radiobutton löst keine neue Animation aus. Wenn man mit dieser Einschränkung nicht leben möchte, kann man mit einer doppelten Steuerung arbeiten. Die Idee ist, die Anzahl der Radiobuttons zu verdoppeln und die beiden Hälften mit zwei getrennten Zusammenstellungen von Labels zu steuern. Durch die Trennung, lassen sich die beiden Labelgruppen jeweils wechselseitig sichtbar schalten - ist ein zur ersten Steuerung zugehöriger Radiobutton ausgewählt, wird die zweite Gruppe von Labeln angezeigt und umgekehrt. Wir brauchen also etwas zusätzlichen HTML-Code:
Dieses zusätzliche Markup zieht einen Rattenschwanz an weiteren Stylesheets nach sich. Da bei jedem Klick auf ein Label, die zugehörige Animation von vorne starten muss, können wir den Buttons der zweiten Gruppe nicht die gleichen Animationen zuordnen. Folgendes reicht leider nicht:
#slide01:checked ~ .slideList, #slide101:checked ~ .slideList { animation-name: slidesStart01; left: 0; }
Es müssen die gleichen Animationen unter einem neuen Namen angelegt und separat aufgerufen werden - z.B. so:
@keyframes slidesStart01 { 0%, 15% {left: 0;} 25%, 40% {left: -100%;} 50%, 65% {left: -200%;} 75%, 90% {left: -300%;} 100% {left: -400%;} } #slide01:checked ~ .slideList { animation-name: slidesStart01; left: 0; } @keyframes slidesStart101 { 0%, 15% {left: 0;} 25%, 40% {left: -100%;} 50%, 65% {left: -200%;} 75%, 90% {left: -300%;} 100% {left: -400%;} } #slide101:checked ~ .slideList { animation-name: slidesStart101; left: 0; }
Analog hierzu wird der gleiche Schritt auch für die Animationen, die den ersten Slide ans Ende schieben, notwendig.
Hervorhebung der Labels
Zur Kennzeichnung des zum jeweils ausgewählten Radiobuttons zugehörigen Labels werden ebenfalls Keyframe-Animationen definiert. Hier muss wie bereits gewohnt für jeden Startpunkt wieder eine separate Definition erstellt werden, was dann für die erste Labelgruppe wie folgt aussehen kann:
@keyframes slideControl01 { 24.99% {background: #68b022;} 25%, 100% {background: none;} } @keyframes slideControl02 { 24.99%, 50% {background: none;} 25%, 49.99% {background: #68b022;} } @keyframes slideControl03 { 49.99%, 75% {background: none;} 50%, 74.99% {background: #68b022;} } @keyframes slideControl04 { 74.99% {background: none;} 75%, 100% {background: #68b022;} } .slideControl label { animation: 20s 2s infinite; } #slide01:checked ~ .slideControl label[for="slide01"], #slide02:checked ~ .slideControl label[for="slide02"], #slide03:checked ~ .slideControl label[for="slide03"], #slide04:checked ~ .slideControl label[for="slide04"] { animation-name: slideControl01; background: #68b022; } #slide01:checked ~ .slideControl label[for="slide02"], #slide02:checked ~ .slideControl label[for="slide03"], #slide03:checked ~ .slideControl label[for="slide04"], #slide04:checked ~ .slideControl label[for="slide01"] { animation-name: slideControl02; } #slide01:checked ~ .slideControl label[for="slide03"], #slide02:checked ~ .slideControl label[for="slide04"], #slide03:checked ~ .slideControl label[for="slide01"], #slide04:checked ~ .slideControl label[for="slide02"] { animation-name: slideControl03; } #slide01:checked ~ .slideControl label[for="slide04"], #slide02:checked ~ .slideControl label[for="slide01"], #slide03:checked ~ .slideControl label[for="slide02"], #slide04:checked ~ .slideControl label[for="slide03"] { animation-name: slideControl04; }
Die zweite Gruppe wird analog gesteuert und die Sichtbarkeit der Gruppen über die Positionierung mit folgendem Code geregelt:
.control01:checked ~ .slideControl01, .control02:checked ~ .slideControl02 { left: -5000px; }
Damit ist man durch und hat einen komplett funktionstüchtigen Slider inkl. Steuerelemente für beliebige Inhalte, der ohne eine Zeile Javascript auskommt.
Fazit
Man kann mittlerweile eine rein CSS-basierte Lösung für Slider und ähnliche Bausteine schreiben, die sogar von den meisten Browsern verstanden wird, d.h. mit Vendor-Präfix kommt so ziemlich jeder moderne Browser damit klar. Eine ausführliche Übersicht, welche Browser Keyframe-Animationen unterstützen gibt es auf caniuse.com.
Unbestritten ist sicherlich der Mehraufwand, im Vergleich zur Verwendung einer fertigen (Javascript-)Lösung. Wobei man die dem CSS-Code zugrundeliegende Logik natürlich auch mit einer beliebigen Programmiersprache abbilden und ihn so automatisch erzeugen lassen kann.
Andere Verhalten, als der Verschub von rechts nach links (Überblendungen, Drehungen, Skalierungen etc.) lassen sich über die Manipulation der entsprechenden CSS-Eigenschaften natürlich problemlos bauen.