SVG/Tutorials/Einstieg/SVG mit CSS animieren
Text-Info
- 30min
- mittel
- Kenntnisse in
• Grundformen in SVG
• CSS-Animation
In der SVG-Welt können Sie mit SMIL (auszusprechen wie englisch: smile – lächeln) Elemente direkt animieren. Genau so einfach ist die Verwendung der CSS-Eigenschaften transition und animation. Sie haben gegenüber SMIL, das jeweils einem Element als Kindelement zugeordnet wird, den Vorteil, dass CSS-Animationen im Stylesheet einmal definiert und dann mehrfach aufgerufen werden können. Die hier vorgeschlagenen Transformationen laufen schneller, da sie hardwarebeschleunigt sind, d. h. dass rechenintensive Operationen an die GPU ausgelagert werden. So können Animationen glatt und ruckelfrei ausgeführt werden.
Google sieht dies in Verbindung mit der Web Animations API als zukünftige Methode Animationen im Webdesign zu steuern.
Inhaltsverzeichnis
Animationen
Die Eigenschaft animation ruft keyframes auf, in denen Sie die einzelnen Schritte einer CSS-Animationssequenz festlegen. Diese Schritte werden durch Wegpunkte gesetzt, die während der Sequenz an bestimmten Punkten erreicht werden. Den zu animierenden CSS-Eigenschaften werden dort unterschiedliche Werte zugeordnet.
#loading {
animation-name: changing;
animation-duration: 4s;
animation-direction: alternate;
animation-iteration-count: infinite;
}
@keyframes changing {
0% {
fill:darkgreen;
width: 20px;
}
100% {
fill: lime;
width: 200px;
}
}
Das rect-Element wird über seine id selektiert. Ihm wird eine Animation mit dem Namen loading
zugewiesen. Diese erhält über …
- animation-duration eine Dauer von 4s;
- animation-direct: alternate, bewirkt eine Wechsel der Abspielrichtung, dass sie immer im Wechsel vor- und wieder rückwärts abgespielt wird
- animation-iteration-count: infinite sorgt dafür, dass die Animation unendlich läuft.
rx,
ry
und hier width
eine Angabe in Pixeln, obwohl es eigentlich dimensionslose Größen sind.Laufende Linien
Eine Spezialität von SVG sind animierte Randlinien; auf Englisch auch line-drawing genannt. In diesem einfachen Beispiel wird ein Ladebalken bzw ein loading spinner animiert.
.loading {
fill: none;
stroke: #306f91;
stroke-width: 10;
stroke-dasharray: 30 10;
animation: strokeAni .7s infinite linear;
}
.inner {
stroke: #c32e04;
animation-direction: reverse;
}
@keyframes strokeAni {
0% {
stroke-dashoffset: 40;
}
100% {
stroke-dashoffset: 0;
}
}
<path id="linie" d="m20,100 h600" />
<path d="m620,80 v40 l20,-20z" fill="#3983ab"/>
Die Linie besteht aus einem Pfad, der mit mit der Eigenschaft stroke-dasharray einen gestrichelten Rand erhält. Die Werte legen genau fest, wie viel „Randlinie“ und wie viel Lücke gezeichnet werden soll.
Alle Elemente der Klasse .loading
rufen nun die Animation strokeAni
auf. In dieser wird mit stroke-dashoffset der Beginn des Randes verschoben, sodass es aussieht, als ob die Striche sich bewegen. Der Verlauf erhält den Wert linear, damit sie gleichmäßig verläuft und wird wegen infinite
unendlich wiederholt.
Der Spinner besteht aus zwei Kreisen ohne Füllung, deren Randlinie genau wie oben beschrieben gestrichelt ist. Für den circle mit der Klasse .inner
wird ebenfalls die Animation strokeAni
aufgerufen, mit animation-direction:reverse
aber eine andere Richtung festgelegt.
Line-Drawing
Wenn Sie gestrichelte Linien mit einem hohen Wert für stroke-dasharray versehen und diesen dann mit stroke-dashoffset so weit verschieben, dass die Randlinie ursprünglich nicht sichtbar ist, können Sie den Eindruck erwecken, dass das SVG-Objekt gerade gezeichnet wird.
path {
stroke: #c32e04;
fill: none;
stroke-width: 3;
stroke-dasharray: 900 900;
animation: strokeAni 5s infinite linear;
}
@keyframes strokeAni {
0% {
stroke-dashoffset: 900;
}
100% {
stroke-dashoffset: 0;
}
}
<path d="M200,350 v-100 l-50,-51 l-50,52 h100 l-100,100 v-100 l100,100 h-100" />
Siehe auch
Transformationen
Für Transformationen gibt es in SVG das transform-Attribut und seine Verwandten gradientTransform bei Verläufen und patternTransform bei Mustern. Mit ihnen können Größenänderungen wie Skalierungen, Streckungen und Stauchungen, aber auch Drehungen und Verschiebungen vorgenommen werden. In CSS gibt es die gleichnamige transform-Eigenschaft, die sich in den allen modernen Browsern auch auf SVG-Elemente anwenden lässt.
Trotz ihrer Ähnlichkeit gibt es gravierende Unterschiede in den Koordinatensystemen, auf denen die Transformationen basieren. Während jedes HTML-Element sein internes Koordinatensystem hat, dessen Ursprung bei (50%, 50%)
liegt, beziehen sich SVG-Elemente immer auf das Koordinatensystem des svg-Elements, das seinen Ursprung in der linken oberen Ecke bei (0|0)
hat.
Deshalb erzielen SVG-Transformationen ganz andere Ergebnisse als Transformationen mit CSS.
@keyframes rotation {
0% { transform: rotate(0deg);}
100% {transform: rotate(360deg);}
}
@keyframes rotation2 {
0% {transform: translate(250px,100px) rotate(0deg);}
100% {transform: translate(250px,100px) rotate(360deg);}
}
<rect id="eins" x="100" y="100" width="50" height="50"/>
<rect id="zwei" x="0" y="0" width="50" height="50" />
<rect id="drei" x="-25" y="-25" width="50" height="50"/>
Das blaue Quadrat hat seinen Mittelpunkt ebenfalls durch die x- und y-Koordinaten im Ursprung des Koordinatensystems. Deshalb dreht es sich um sich selbst und wird dann erst mit
translate(250,100)
passend verschoben.translate(x,y)
.
Ana Tudor hat einen sehr ausführlichen Artikel auf CSS-Tricks veröffentlicht, in dem die systembedingten Unterschiede und auch die abweichenden Browser-Verhalten vorgestellt werden.[1] In fernerer Zukunft sollen die beiden Spezifikationen zu einer einheitlichen verschmelzen, so wie es bei Masken und Beschneidungen schon erfolgt ist.
SVG-Uhr
Als Anwendungsbeispiel wollen wir nun eine Uhr erstellen, die mit SVG gezeichnet und mit CSS-animation und transform zum Laufen gebracht wird.
Zifferblatt
Wie oben gesehen, gehen Transformationen immer vom Ursprung des Kordinatensystems aus. Also liegt es nahe, dem Mittelpunkt der Uhr, an dem sich auch die Zeiger orientieren, auf 0|0 festzulegen.
Im Normalfall wäre dies die linke, obere Ecke des SVG-Elements. Durch die viewBox können wir es aber passend verschieben, dass der Ursprung in der Mitte liegt:
<svg viewBox="-100 -100 200 200">
<circle id="clockface" r="98" />
<circle id="origin" r="2" />
</svg>
Die beiden circle-Elemente benötigen keine cx
- und cy
-Angaben, da für diese der Standardwert genommen wird.
Während der Ursprung (engl. origin) nur aus einem 4px breiten Punkt besteht (d = 2r), wird das Zifferblatt mit CSS gestylt.
Eine Möglichkeit wäre das Einfügen eines Fotos als Hintergrundbild einer Uhr. Wir wollen jedoch die Indizes für die Stunden per SVG einfügen, damit sie später mit CSS beliebig gestylt werden können.
<svg viewBox="-100 -100 200 200">
<defs>
<line id="index" x1="80" y1="0" x2="90" y2="0" />
</defs>
<circle id="clockface" r="98" />
<circle id="origin" r=" 2" />
<g id="indizes">
<use xlink:href="#index" transform="rotate(300)" />
<use xlink:href="#index" transform="rotate(330)" />
<use xlink:href="#index" transform="rotate(0)" />
<use xlink:href="#index" transform="rotate(30)" />
<use xlink:href="#index" transform="rotate(60)" />
<use xlink:href="#index" transform="rotate(90)" />
...
</g>
</svg>
Im Definitionsabschnitt wird ein line-Element notiert, dass aber nicht gerendert wird.
Mit dem use-Element wird es über die ID mehrfach aufgerufen und dabei mit dem transform-Attribut entsprechend (360° / 12h = 30°) gedreht. Diese Transformation ist bewusst im SVG-Markup geblieben, da sie ja eher zum Inhalt gehört und nicht durch einen anderen Stil geändert werden sollte.
CSS
Nur die Darstellung an sich wird im CSS-Abschnitt festgelegt.
Dabei verwenden wir für die Farben custom properties, damit Sie die Farbgestaltung später schnell ändern können.
svg {
--bgcolor: #fdfcf3;
--primarycolor: #306f91;
--accent1color: #dfac20;
--accent2color: #c32e04;
}
#indizes > use {
stroke: var(--accent1color);
stroke-width: 1;
stroke-linecap: round;
}
#indizes > use:nth-child(3n+3) {
stroke-width: 3;
}
Warum startet die Reihe der use-Elemente mit rotate(300)
? Über den use:nth-child(3n+3)
-Selektor wird jedes dritte use-Element (≙ 3, 6, 9, 12 Uhr) selektiert und entsprechend deutlicher markiert.
Zeiger
Genauso können wir mit den drei Zeigern vorgehen:
<svg viewBox="-100 -100 200 200">
<defs>
</defs>
<circle id="clockface" r="98" />
<g id="indizes">
...
</g>
<line class="hand" id="hours" x1="0" y1="0" x2="0" y2="-50" />
<line class="hand" id="minutes" x1="0" y1="0" x2="0" y2="-95" />
<g id="seconds" class="hand">
<line x1="0" y1="0" x2="0" y2="-55" />
<circle cx="0" cy="-60" r="5" fill="none" />
<line x1="0" y1="-65" x2="0" y2="-95" />
</g>
<circle id="origin" r="2" />
</svg>
Die beiden ersten Zeiger bestehen aus line-Elementen, die eine gemeinsame Klasse und individuelle IDs erhalten.
Der Sekundenzeiger besteht aus zwei Linien, die durch einen circle
verbunden werden. Da die drei Elemente innerhalb eines g-Elements gruppiert wurden, erhält dieses die Klassen und Id, deren Festlegungen dann auf die Kindelemente vererbt werden.
Da SVG-Elemente der Reihe nach gerendert werden, würden sie jetzt über dem Ursprungspunkt liegen, weswegen das circle-Element nun ans Ende des SVG-Abschnitts verschoben wurde.
#indizes > use,
.hand {
stroke: var(--primarycolor);
stroke-width: 1;
stroke-linecap: round;
}
#indizes > use:nth-child(3n+3) {
stroke-width: 3;
}
#hours {
stroke: var(--primarycolor);
stroke-width: 4;
}
#minutes {
stroke: var(--primarycolor);
stroke-width: 2;
transform: rotate(33deg);
}
#seconds {
stroke: var(--accent2color);
transform: rotate(9deg);
}
Der Stundenzeiger wird 4x breiter, der Minutenzeiger doppelt so breit wie der Sekundenzeiger. Dieser wird rot eingefärbt. Normalerweise würden die drei Zeiger übereinanderliegen. Damit sie jetzt schon sichtbar sind, wurden Minuten- und Sekundenzeiger mit CSS-transform gedreht.
Nun haben wir ein schönes Bild einer Uhr. Perfekt wäre es allerdings, wenn sich die Zeiger bewegen würden.
Animation
Mit CSS-animations können wir keyframes
definieren, innerhalb der bestimmte Eigenschaften ihre Werte ändern. Das zu animierende Element erhält nun eine Eigenschaft animation
, in der dieser keyframe aufgerufen wird:
svg {
--start-hours: 0;
--start-minutes: 0;
--start-seconds: 0;
height: 250px;
}
#seconds {
stroke: #c32e04;
transform: rotate(calc(var(--start-seconds) * 6deg));
animation: rotateSecondsHand 60s steps(60) infinite;
}
@keyframes rotateSecondsHand {
from {
transform: rotate(calc(var(--start-seconds) * 6deg));
}
to {
transform: rotate(calc(var(--start-seconds) * 6deg + 360deg));
}
}
Zuerst werden nun CSS-Variablen für Stunden, Minuten und Sekunden gesetzt. Diese werden aber erst später gebraucht – im jetzigen Beispiel sind sie noch nicht gesetzt.
Der Sekundenzeiger erhält eine animation
-Eigenschaft, mit der der keyframe rotateSecondsHand
aufgerufen wird. Hier wird eine Drehung des Zeigers erreicht, indem der transform-Eigenschaft die calc()-Funktion calc(var(--start-seconds) * 6deg)
als Wert zugewiesen wird. So wird der Sekundenzeiger innerhalb der Dauer der Animation (60s) um 360° gedreht. Dabei verläuft die Animation nicht durchgehend, sondern wird mit steps(60)
in 60 Schritte geteilt – so springt der Zeiger jede Sekunde eine Position weiter.
Analog werden die Minuten berechnet und der Minutenzeiger entsprechend gedreht:
#minutes {
stroke-width: 2;
transform: rotate(calc(var(--start-minutes) * 6deg));
animation: rotateMinuteHand 3600s steps(60) infinite;
animation-delay: calc(var(--start-seconds) * -1 * 1s);
}
@keyframes rotateMinuteHand {
from {
transform: rotate(calc(var(--start-minutes) * 6deg));
}
to {
transform: rotate(calc(var(--start-minutes) * 6deg + 360deg));
}
}
Die Animation verläuft vergleichbar der Drehung der Sekundenzeiger; über animation-delay wird aber so lange gewartet, bis der Sekundenzeiger seine 360°-Drehung vollendet und die "12" oben erreicht.
#hours {
transform: rotate(calc(var(--start-hours) * 30deg));
animation: rotateHourHand calc(12 * 60 * 60s) linear infinite;
animation-delay: calc(calc(var(--start-minutes) * -60 * 1s) + calc(var(--start-seconds) * -1 * 1s));
}
@keyframes rotateHourHand {
from {
transform: rotate(calc(var(--start-hours) * 30deg));
}
to {
transform: rotate(calc(var(--start-hours) * 30deg + 360deg));
}
}
Analog wird der Stundenzeiger gesteuert. Da wir die Startzeit auf 0:00:00 gesetzt haben, ist dies nun eine gute Uhr, die die Zeit seit dem Aufruf des Beispiels misst. Besser wäre allerdings eine Uhr, die die aktuelle Zeit angibt. Dies ist mit CSS alleine aber nicht möglich.
Zeitabfrage
Nach dem Prinzip der Trennung von Inhalt, Präsentation und Verhalten sind HTML und SVG für die Auszeichnung des Inhalts, CSS für die Darstellung und JavaScript für Interaktion zuständig. Um die Systemzeit des Computers oder eine Zeit im Internet abzufragen, benötigen Sie JavaScript.
Theoretisch kann das script-Element auch innerhalb des svg-Elements notiert werden. Besser ist es jedoch, dies im head des HTML-Dokuments zu tun:
document.addEventListener('DOMContentLoaded', function () {
const svg = document.querySelector('svg');
const currentTime = new Date();
svg.style.setProperty('--start-seconds', currentTime.getSeconds());
svg.style.setProperty('--start-minutes', currentTime.getMinutes());
svg.style.setProperty('--start-hours', currentTime.getHours() % 12);
});
Das Script beinhaltet einen Eventlistener, der nach dem Laden der Webseite mit new Date() ein neues Datumsobjekt mit der aktuellen Zeit (engl. currentTime) erzeugt.
Mittels setProperty() wird dann die aktuelle Zeit den CSS-Variablen zugewiesen.
Fazit:
Mit nur 5 Zeilen JavaScript und drei ähnlichen CSS-Animationen, der Verwendung von CSS-Variablen für Farbe und die Zeitwerte ist eine semantisch passende, und übersichtliche Uhr gelungen. Vielen Dank an John O. Paul für sein englischsprachiges Tutorial, aus dem ich die Animationen verwendet habe.
Die unten verlinkten Beispiele funktionieren mit SMIL und JavaScript und können als Anregungen für weitere Uhren-Themes dienen.
Weblinks
- css-tricks: SVG Properties and CSS (13.05.2019)
- css-tricks: Animating SVG with CSS
- smashing-magazine: Styling and animating SVG with CSS von Sara Soueidan (03.11.2014)
- Hongkiat: How To Create SVG Animation Using CSS
- Tavmjong Bah's Blog Animation in SVG and CSS (The digital river)
- Animated Animals in CSS and SVG David Khourshid (21.03.2016)
Uhren
- David Dailey, SRU: Ball Clock
- tavmjong.free.fr: SteamEngine.svg (ein schöner Zeiger sogar mit Schattenwurf) von Tavmjong Bah
- tavmjong.free.fr: Gear Clocks von Tavmjong Bah
Quellen
- ↑ css-tricks: Transforms on SVG Elements
stroke-daharray
festgelegt wird. Der Wert von 900 ist so hoch, dass die Strichelung länger als der Umriss des Hauses ist. Da er zu Beginn der Animation mitstroke-dashoffset
um 900 verschoben wurde, erscheint der Rand allmählich und es entsteht ein Zeicheneffekt.