SVG/Tutorials/Einstieg/SVG mit CSS animieren

Aus SELFHTML-Wiki
< SVG‎ | Tutorials‎ | Einstieg
Wechseln zu: Navigation, Suche

Informationen zu diesem Text

Lesedauer
30min
Schwierigkeitsgrad
mittel
Vorausgesetztes Wissen
Kenntnisse in
• HTML
• CSS

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.

Animationen[Bearbeiten]

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.

  • Chrome
  • Firefox
  • Edge
  • Opera
  • Safari
Ladebalken ansehen …
#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 …

Beachten Sie: Der Firefox benötigt für die Präsentationsattribute wie rx, ry und hier width eine Angabe in Pixeln, obwohl es eigentlich dimensionslose Größen sind.

Laufende Linien[Bearbeiten]

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.

Ladebalken und Spinner ansehen …
.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[Bearbeiten]

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.

Haus vom Nikolaus ansehen …
path {
  stroke: #c32e04;
  fill: none;
  stroke-width: 3px;
  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" />
Die Randlinie hat eine Strichelung, deren Wert mit 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 mit stroke-dashoffset um 900px verschoben wurde, erscheint der Rand allmählich und es entsteht ein Zeicheneffekt.

Transformationen[Bearbeiten]

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.

Beispiel ansehen …
@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 rote und das gelbe Rechteck drehen sich um 360 Grad, nehmen als Mittelpunkt der Drehung aber den Ursprung des SVG-Koordinatensystems.
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.
Empfehlung: Verzichten Sie auf eine Angabe von x- und y-Koordinaten, zentrieren Sie SVG-Objekte um den Ursprung und positionieren Sie sie erst nach der gewünschten Transformation mit 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[Bearbeiten]

Als Anwendungsbeispiel wollen wir nun eine Uhr erstellen, die mit SVG gezeichnet und mit CSS-animation und transform zum Laufen gebracht wird.

Zifferblatt[Bearbeiten]

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:

Viewbox mit verschobenem Ursprung ansehen …
<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.

Zifferblatt mit Stunden-Indizes ansehen …
<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[Bearbeiten]

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.

CSS der Stundenmarkierungen ansehen …
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[Bearbeiten]

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.

Gestaltung der Zeiger ansehen …
#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[Bearbeiten]

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:

Sie läuft und läuft … ansehen …
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:

Animation des Minutenzeigers ansehen …
#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.

Animation des Stundenzeigers ansehen …
#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[Bearbeiten]

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:

Zeitabfrage in JavaScript ansehen …
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[Bearbeiten]

Uhren

Quellen[Bearbeiten]

  1. css-tricks: Transforms on SVG Elements