Kreise animieren (1)

Runde Fortschrittsbalken mit CSS

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.

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.

Schreibe einen Kommentar

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