Wenn man einen hübschen runden Fortschrittsbalken, Timer oder Counter für eine Webseite benötigt, stolpert man meist über zwei verschiedene Herangehensweisen: Entweder man arbeitet in CSS mit transform:rotate und dreht absolut positionierte HTML-Elemente hinter Masken und 'hidden overflows' oder man nimmt einen svg circle mit stroke-dasharray und stroke-dashoffset zur Hilfe.
Die transform-Variante gibt es in diesem Artikel, die SVG-Variante in einem anderen.
Inhalt
1. Worum es geht…
… zeigt das Video im Schnelldurchlauf:
2. Das Grundgerüst
Zuerst bastelt man sich einen Wrapper oder eine "Bühne", in dem man mit einem relativ positionierten Eltern-<div>
mit absolut positionierten Kind-Elementen hantieren kann. Insgesamt benötigt man vier Kind-<div>
s:
HTML
<div id="stage"> <div class="circle"> <div class="rightcircle"></div> <div class="leftcircle"></div> <div class="mask"></div> <div class="innercircle"></div> </div> </div>
CSS
#stage { width:20rem; height:20rem; /*width = height*/ background:#f2efef; } .circle { position:relative; width:15rem; /*muss kleiner als #stage sein*/ height:15rem; /*width = height*/ top:50%; left:50%; transform:translate(-50%,-50%); border-radius:50%; background:#bf304a; }
3. Die Kreisbewegung (rechts)
Jetzt platziert man ein Rechteck über der linken Hälfte des Kreises und setzt den Ankerpunt (CSS: transform-origin
) rechts mittig, um es via transform:rotate
zu drehen.
CSS
.rightcircle { position:absolute; left:0; top:0; width:50%; height:100%; transform-origin:right center; animation:rightcircle 5s linear infinite; } @keyframes rightcircle { 100% {transform:rotate(360deg);} }
Die Animation dient hier erst einmal nur zur Verdeutlichung – sie wird im weiteren Verlauf noch weiter modifiziert.
Wenn man nun für .circle
via CSS overflow:hidden;
setzt und einen .innercircle
gestaltet, der die gleiche Farbe wie der Hintergrund hat, wird schnell deutlich, wie das Ganze am Ende funktioniert. Allerdings gibt es einen Haken – die Bewegung des Rechtecks reicht nur für die rechte Kreishälfte. Daher stoppt die Animation bei 50% der Zeit bei 180 Grad und bleibt dort bis zum Ende stehen):
CSS
.circle { /* siehe oben */ overflow:hidden; } .innercircle { position:absolute; background:#f2efef; /* entspricht background von #stage */ top:50%; left:50%; transform:translate(-50%,-50%); width:10rem; height:10rem; border-radius:50%; } @keyframes rightcircle { 50% {transform:rotate(180deg);} 100% {transform:rotate(180deg);} }
4. Die Kreisbewegung (links)
Für die linke Hälfte nehmen wir nun das zweite Kind-Element .leftcircle
, welches zuerst mit .rightcircle
"mitläuft", dann aber zusätzlich die zweite Hälfte der Kreisbewegung macht – erst wieder mit overflow:visible;
für .circle
und ohne .innercircle
:
CSS
.rightcircle, .leftcircle { position:absolute; left:0; top:0; width:50%; height:100%; transform-origin:right center; } .rightcircle { animation:rightcircle 5s linear infinite; } .leftcircle { animation:leftcircle 5s linear infinite; } @keyframes rightcircle { 50% {transform:rotate(180deg);} 100% {transform:rotate(180deg);} } @keyframes leftcircle { 100% {transform:rotate(360deg);} }
Mit den entsprechenden Masken (.circle
bekommt overflow:hidden;
, .innercircle
eingeblendet) sieht das dann so aus:
5. Maskierung
Das ist schon nah dran. Lediglich der Anfang der Animation ist noch nicht sooo super 🙈 daher kommt hier das dritte Kind-Element .mask
ins Spiel. Damit wird für die Hälfte der Animation die linke Seite des Kreises "wegmaskiert", indem man die Deckkraft (opacity
) animiert. Grün ist natürlich auch "suboptimal", hier nur zur Verdeutlichung:
CSS
.mask { position:absolute; left:0; top:0; width:50%; height:100%; background:#86bf30; animation:mask 5s linear infinite; } @keyframes mask { 50% {opacity:1;} 50.01% {opacity:0;} }
Wenn man nun background von .circle
und .mask
an #stage
anpasst, ist es schon fertig:
In manchen Browsern kommt es zu schwer zu unterdrückenden Artefakten in der ersten Hälfte der Animation (ein "Schweif" von .rightcircle
, der entlang der Kontur von .circle
gezogen wird). Die kann man z.B. mit einer border
auf .circle
verschleiern.
6. Fertig 🙂
Hier noch einmal der gesamte und bereinigte Code …
HTML
<div id="stage"> <div class="circle"> <div class="rightcircle"></div> <div class="leftcircle"></div> <div class="mask"></div> <div class="innercircle"></div> </div> </div>
CSS
#stage { width:20rem; height:20rem; /*width = height*/ background:#f2efef; } .circle { position:relative; width:15rem; /*muss kleiner als #stage sein*/ height:15rem; /*width = height*/ top:50%; left:50%; transform:translate(-50%,-50%); border-radius:50%; background:#f2efef; /* muss background von #stage entsprechen */ overflow:hidden; border:1px solid #aaa; } .innercircle { position:absolute; background:#f2efef; /* muss background von #stage entsprechen */ top:50%; left:50%; transform:translate(-50%,-50%); width:10rem; /*muss kleiner als .circle sein*/ height:10rem; /*width = height*/ border-radius:50%; } .rightcircle, .leftcircle, .mask { position:absolute; left:0; top:0; width:50%; height:100%; transform-origin:right center; } .rightcircle, .leftcircle { background:#30a3bf; /* Farbe des Kreises */ } .mask { background:#f2efef; /* muss background von #stage entsprechen */ } .rightcircle { animation: rightcircle 5s linear infinite; } .leftcircle { animation: leftcircle 5s linear infinite; } .mask { animation: mask 5s linear infinite; } @keyframes rightcircle { 50% {transform:rotate(180deg);} 100% {transform:rotate(180deg);} } @keyframes leftcircle { 100% {transform:rotate(360deg);} } @keyframes mask { 0% {opacity:1;} 50% {opacity:1;} 50.01% {opacity:0;} 100% {opacity:0;} }
7. Varianten
Die Animation kann natürlich getriggert werden – z.B. durch :hover
oder via JavaScript – und wenn sie nicht in Dauerschleife laufen soll, muss bei allen animation
-Angaben der animation-iteration-count von infinite
gelöscht oder auf 1 gesetzt werden. Darüber hinaus empfiehlt sich ein animation-fill-mode von forwards
. Damit wird die Animation auf dem letzten Frame (der 100%-Einstellung) angehalten und springt nicht am Ende auf die 0%-Einstellung zurück. Die shorthand-Angabe geht dann z.B. so: animation: rightcircle 5s linear 1 forwards;
7.1 Trigger via :hover
7.2 Trigger via JavaScript
7.3 Dynamische Manipulation via jQuery
So weit. Viel Spaß beim Basteln 🙂
Verbesserungen, Anregungen, Anmerkungen gerne in die Kommentare.
"Kreise animieren (1)" 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.