Neben der hier beschriebenen Art, HTML-div
s via CSS (transform:rotate) zu animieren und zu maskieren, ist auch eine weitere Variante verbreitet: SVG circles.
Diese vektorisierten Grafiken sind recht einfach direkt im HTML-Code generierbar und ihre Eigenschaften via CSS oder JS manipulierbar. Im vorliegenden Fall kann man die Attribute stroke („Pfadkontur“), stroke-dasharray (in etwa: „gestrichelte Kontur“) und stroke-dashoffset (meint so etwas wie: „Verschiebung der gestrichelten Kontur“) nutzen, um drei Kreiskonturen zu animieren:
Inhalt
1. Worum es geht…
… zeigt folgendes Video im Schnelldurchlauf:
2. Das Grundgerüst
Auch hier bastele ich zuerst eine Bühne (#stage
), auf der ich dann mit weiteren Elementen hantiere. Im vorliegenden Fall ist das ein <svg>
-Element mit dem Inhalt <circle>
:
HTML
<div id="stage"> <svg viewBox="0 0 100 100" width="20rem" height="20rem"> <circle cx="50" cy="50" r="40" class="circle1"></circle> </svg> </div>
CSS
#stage { background: #111213; width: 20rem; height: 20rem; margin: 2rem auto; border-radius: 2rem; position: relative; } .circle1 { fill:#bf304a; }
Die viewBox
(Doku) öffnet eine Art Zeichenfläche, die über Koordinaten und Größenangaben definiert wird ("x-Ankerpunkt y-Ankerpunkt Breite Höhe"). Die Größenangaben der viewBox sind relativ zur width
– und height
-Angabe des svg
-Elements zu verstehen, funktionieren daher ohne Einheiten (px, %, em, …) und regeln so vor allem a) das Seitenverhältnis der Zeichenfläche und b) die Positionierung von gezeichneten Objekten.
Die Angaben cx
(= center x-axis), cy
(= center y-axis), r
(= Radius), fill
(= Füllung), stroke
(= Kontur) und weitere SVG Attribute können sowohl als CSS (z.B. cx:10rem;
) oder als Element-Attribut in HTML (dann: <svg><circle cx="160"></circle></svg>
) angegeben werden. Bei der Angabe direkt im HTML-Code ist eine Angabe verlässlicher und cross-brower-geeigneter, dann ebenfalls ohne Einheiten vorzunehmen. ein cx
& cy
von je 50 legt den Mittelpunkt des Kreises also genau in die Mitte der viewBox
mit den Abmessungen 100 * 100:
Für mehre Ringe benötigen wir nun innerhalb des einen <svg>
drei <circle>
, und statt einer Füllung (fill
) wird nun eine Konturfarbe (stroke
) eingesetzt, der man eine Breite (stroke-width
) geben kann. Bei einer Konturbreite von 2rem
verringert sich der Radius r
für jeden folgenden Kreis um ebenfalls 2rem
(weil wegen Kontur rundherum, sind also eigentlich 4 rem und Radius ist nur der halbe Durchmesser … ach, Ihr wisst schon):
HTML
<div id="stage"> <svg viewBox="0 0 100 100" width="20rem" height="20rem"> <circle cx="50" cy="50" r="40" class="circle1"></circle> <circle cx="50" cy="50" r="30" class="circle2"></circle> <circle cx="50" cy="50" r="20" class="circle3"></circle> </svg> </div>
CSS
.circle1 { fill:none; stroke:#ff284f; stroke-width:10; } .circle2 { fill:none; stroke:#b3ff41; stroke-width:10; } .circle3 { fill:none; stroke:#11b4f4; stroke-width:10; }
So siehts aus:
3. Konturgestaltung
Jetzt wirds ein wenig frickelig, da die Konturlinie mittels stroke-dasharray
in Segmente "zerlegt" werden muss und diese Segmente mittels stroke-dashoffset
verschoben werden. Dafür benötigt man die Länge der Kontur (man könnte sie auch "Umfang" nennen 🙈).
3.1 stroke-dasharray
Bei Kreisen ist der Umfang bekanntlich bei U = π * 2 * r
, also für den äußeren Kreis bei 3,1415… * 2 * 40 = ~ 251,3. Wenn man beispielhaft das dasharray mal auf 1/10 des Umfangs (= 25.13
… Punkt statt Komma 🙄) setzt, erhält man fünf Abschnitte und fünf "Lücken":
CSS
.circle1 { /* siehe oben */ stroke-dasharray: 25.13; }
3.2 stroke-dashoffset
Die Offset-Einstellung kann nun diese Kontur-Segmente verschieben. Eine Animation von stroke-dashoffset
um 50.26;
(1 Strich + 1 Lücke, je 25.13;
) führt zu einer lustigen Kreisbewegung:
CSS
.circle1 { /* siehe oben */ stroke-dasharray: 25.13; animation: strokes 1s linear infinite; } @keyframes strokes { 100% {stroke-dashoffset: 50.26;} }
3.3 Einsatz von Array & Offset für eine Animation
Wenn man das Grundprinzip einmal (halbwegs! gilt für mich) begriffen hat, wird klar: Wenn stroke-dasharray = Umfang
ist, ist der Kreis "unsichtbar", da die erste Lücke der Segmentierung genau so groß wie die gesamte Kreislinie lang ist. Und wenn man dann stroke-dashoffset
von x nach 0 animiert (bei xmax = Umfang), läuft die Kreiskontur "von 0 auf voll":
CSS
.circle1 { /* siehe oben */ stroke-dasharray: 251.3; /* entspricht dem Umfang π * 2 * r */ stroke-dashoffset: 251.3; /* entspricht dasharray */ animation: strokes 3s ease-in-out infinite; } @keyframes strokes { 0% {stroke-dashoffset: 251.3;} 50% {stroke-dashoffset: 0;} }
Gleiches Vorgehen für die anderen beiden Kreise – andere Radien machen andere dasharray- und dashoffset-Werte notwendig. Um die Öffnung der Kreise nach oben zu drehen, drehe ich einfach das ganze svg
-Objekt. Zur weiteren Veranschaulichung habe ich ein animation-delay
von .2s
bzw. .4s
für .circle2
und .circle3
eingebaut:
CSS
svg { transform:rotate(-90deg); } .circle1 { /* siehe oben */ stroke-dasharray: 251.3; stroke-dashoffset: 251.3; animation: strokes1 3s ease-in-out infinite; } .circle2 { /* siehe oben */ stroke-dasharray: 188.5; stroke-dashoffset: 188.5; animation: strokes2 3s ease-in-out .2s infinite; } .circle3 { /* siehe oben */ stroke-dasharray: 125.7; stroke-dashoffset: 125.7; animation: strokes3 3s ease-in-out .4s infinite; } @keyframes strokes1 { 0% {stroke-dashoffset: 251.3;} 50% {stroke-dashoffset: 0;} } @keyframes strokes2 { 0% {stroke-dashoffset: 188.5;} 50% {stroke-dashoffset: 0;} } @keyframes strokes3{ 0% {stroke-dashoffset: 125.7;} 50% {stroke-dashoffset: 0;} }
So weit, so cool 🙂
4. Feinschliff
Um das Ganze im Wortsinne abzurunden, kann man mit stroke-linecap
(Doku) runde "Endkappen" aufsetzen, muss dann aber dashoffset evtl. um die "Kappengröße" ändern 🙄
Diese ist im Falle von stroke-linecap:round;
gleich der Hälfte von stroke-width
, hier also 10 / 2 = 5
. Je nach gewünschter Überlappung der beiden Kappen (Anfang und Ende) muss das Offset z.B. bei Endpunkten einer Animation entsprechend vergrößert werden. Wenn sie gar nicht überlappen sollen, muss man Offset auf "zwei Kappengrößen", also eine ganze stroke-width
setzen.
Die Abbildung zeigt schematisch die Funktionsweise von linecaps und die dadurch provozierte Verlängerung einer Konturlinie, das Beispiel unten einen Animations-End-Offset von 7.5
, also "fast" zwei Kappengrößen. Dass nun die Öffnung der kreise nicht mehr exakt oben ist, kann man halbwegs mittels Justierung der Rotation des svg
-Elements ausgleichen.
Eine weitere Option ist das Arbeiten mit Farbverläufen innerhalb von stroke
: Dafür muss man innerhalb des svg
s einen <defs> ... </defs>
-Bereich (Doku) anlegen, dort linearGradient
s definieren und stroke
statt einer Füllfarbe einen Verlauf via ID zuordnen. Standardmäßig verläuft ein Verlauf von oben nach unten, gradientTransform
dreht die Verlaufsrichtung. Die <stop>
-Definitionen legen in % die Punkte über den Verlauf fest, für die Farben hinterlegt sind. Die Ausrichtung von Rotation und Stop-Punkten verlangt je nach Anwendungsfall viel Ausprobieren.
HTML
<svg viewBox="0 0 100 100" width="20rem" height="20rem"> <defs> <linearGradient id="gradient1" gradientTransform="rotate(78)"> <stop offset="32%" stop-color="#fa114f" /> <stop offset="100%" stop-color="#f93885" /> </linearGradient> <linearGradient id="gradient2" gradientTransform="rotate(78)"> <stop offset="32%" stop-color="#99ff01" /> <stop offset="100%" stop-color="#d8ff00" /> </linearGradient> <linearGradient id="gradient3" gradientTransform="rotate(78)"> <stop offset="32%" stop-color="#00d8fe" /> <stop offset="100%" stop-color="#02ffa9" /> </linearGradient> </defs> <circle cx="50" cy="50" r="40" class="circle1"></circle> <circle cx="50" cy="50" r="30" class="circle2"></circle> <circle cx="50" cy="50" r="20" class="circle3"></circle> </svg>
CSS
.circle1 { /*siehe oben*/ stroke:url(#gradient1); } .circle2 { /*siehe oben*/ stroke:url(#gradient2); } .circle3 { /*siehe oben*/ stroke:url(#gradient3); }
5. Varianten
Darüber hinaus kann man auch hier (wie bei der rotierende-divs-Variante) Gedanken über einen Auslöser der Animation machen. Das können auch hier wieder z.B. :hover oder JavaScript-Werte sein.
5.1 Trigger durch :hover
Beim :hover-Trigger empfiehlt es sich, statt mit CSS animations lieber mit CSS transitions zu arbeiten, da diese hübscher in den ":not(:hover)"-Status zurückschwingen. Das ist bei Keyframe-Animationen nicht so einfach zu realisieren:
5.2 Trigger durch JavaScript / jquery
Durch das Binden des Offset-Wertes an eine Variable können die Ringe auch als Fortschritts-, Timer- oder Counter-Anzeige genutzt werden. Hier kommt der Wert – zur Veranschaulichung – aus einem Schieberegler: Close your rings!
So weit.
Ergänzungen, Hinweise, Verbesserungen gerne in die Kommentare. Und – viel Spaß beim Basteln!
"Kreise animieren (2)" 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.