Skip to content

SVG

SVG-Elemente haben geometry und presentational Attribute. Mit den presentational attributes können wir das Aussehen kontrollieren (und im CSS verändern !!). Wir können also entweder inline-Attribute im SVG verwenden oder die Eigenschaft mit einer CSS-Deklaration verändern, wobei CSS die inline-Attribute überschreibt.

<svg width="280" height="280">
<line
x1="80"
y1="80"
x2="200"
y2="200"
stroke="oklch(0.9 0.3 164)"
stroke-width="5"
/>
</svg>
<svg width="280" height="280">
<rect
x="40"
y="80"
width="160"
height="100"
fill="none"
stroke="hotpink"
stroke-width="5"
/>
</svg>

Man kann den Eckradius für x und y separat setzen, aber das Ergebnis sieht dann komisch aus:

<svg width="280" height="280">
<rect
x="80"
y="100"
width="500"
height="500"
rx="100"
ry="50"
stroke="goldenrod"
stroke-width="5"
fill="none"
/>
</svg>

Wenn wir den Radius nur für x setzen, wird y automatisch gleich gesetzt:

<svg width="280" height="280">
<rect
x="80"
y="100"
rx="5"
width="140"
height="100"
stroke="goldenrod"
stroke-width="5"
fill="none"
/>
</svg>

Wenn wir in CSS runde Formen oder Pill-Shapes haben wollen, nehmen wir für den Border-Radius einfach eine absurd hohe Zahl. Ein Border-Radius von 50% macht nämlich nur eine Ellipse (außer bei Quadraten). Mit SVG funktioniert das nicht, das ist aber egal, denn in einem SVG kennen wir üblicherweise die Maße und können den Border-Radius genau auf 50% der Breite oder Höhe setzen.

<svg width="280" height="280">
<circle
cx="120"
cy="120"
r="100"
stroke="cornflowerblue"
stroke-width="5"
fill="none"
/>
</svg>

Fill ist das Äquivalent zu background-color. Die meisten Elemente sind automatisch schwarz gefüllt (was auch bei dark-mode so ist, SVG kennt kein color-scheme)

<svg width="280" height="280">
<circle
class="filled"
cx="120"
cy="120"
r="80"
stroke="cornflowerblue"
stroke-width="5"
fill="none"
/>
</svg>

fill="none" funktioniert übrigens auch im svg-Element selbst, da es (pst!) heimlich verrebbar ist. Wenn also ein SVG mehrere Formen enthält, kann man die mit einer Zeile ungefüllt lassen. Genauso können wir natürlich auch alle anderen Attribute im SVG-Element selbst festlegen! currentColor verwenden wir, wenn z.B. die Farbe eines Icons automatisch die Textfarbe annehmen soll.

Statt fill-opacity können wir auch eine CSS-Farbe mit Alpha-Kanal verwenden.

<svg width="200" height="200">
<circle
cx="100"
cy="100"
r="40"
stroke="tomato"
stroke-width="5"
fill="tomato"
fill-opacity="0.25"
/>
</svg>

Genauso wie die Füllung können wir natürlich auch die Kontur transparent machen:

<svg width="200" height="200">
<circle
cx="100"
cy="100"
r="40"
stroke="tomato"
stroke-width="15"
fill="tomato"
stroke-opacity="0.5"
/>
</svg>

Eine Ellipse ist wie ein Kreis, aber wir können den vertikalen und den horizontalen Radius getrennt setzen:

<svg width="280" height="280">
<ellipse
cx="120"
cy="140"
rx="100"
ry="70"
fill="none"
stroke="rebeccapurple"
stroke-width="5"
/>
</svg>

Mit Polylines können wir nicht nur einfacher mehrere verbundene Linien zeichnen, sondern auch die Verbindungen zwischen den Linien kontrollieren!

<svg width="280" height="280">
<polyline
points="
60,100
100,180
140,140
180,180
220,100
"
fill="none"
stroke="lightsalmon"
stroke-width="5"
/>
</svg>

Polygone sind fast identisch mit Polylines, schließen sich aber automatisch:

<svg width="280" height="280">
<polygon
points="
60,100
100,180
140,140
180,180
220,100
"
fill="lightsalmon"
stroke="tomato"
stroke-width="5"
/>
</svg>

Reguläre oder equilaterale Polygone sind übrigens eine Untergruppe der Polygone!

  • text-anchor: start | middle | end
  • dominant-baseline: auto | middle | hanging
  • Schriftgröße etc. per CSS …
<svg width="280" height="280">
<text
x="140"
y="140"
text-anchor="middle"
dominant-baseline="auto"
fill="midnightblue"
>
Hello <tspan>World!</tspan>
</text>
</svg>
Hello World!

Strokes kann man auch für Texte verwenden, wobei es hier eine Besonderheit bzw. einen kleinen Trick gibt: Da die Outline nicht außen sitzt, sondern von der Mitte des Pfades ausgeht, kann Text mit Outline je nach Schriftart bzw. -größe komisch aussehen. Wir können das aber mit einer etwas obskuren CSS-Eigenschaft austricksen: paint-order. Wenn wir paint-order: stroke fill; setzen, dann wird zuerst die Outline gezeichnet und dann die Füllung. Normalerweise wird der Umriss auf die Füllung gezeichnet, wir kehren die Reihenfolge also um. Die Füllung überdeckt also die Hälfte des Umrisses.

Für ältere Browser könnte man auch den Text layern, also das svg kopieren und eine Variante mit Stroke und ohne Füllung unten und eine ohne Stroke und mit Füllung darüber legen.

Text-Umrisse können auch stroke-linejoin UND wir können das auch für HTML-Texte verwenden, und zwar mit der -webkit-text-stroke Eigenschaft 😄. Das ist aber mit Vorsicht zu genießen, da der Browser-Support nicht unbedingt gut gewährleistet ist. @supports sagt uns auch nur, ob es diese Eigenschaft gibt, und nicht, ob sie auch unterstützt wird.

<svg class="text-stroke" width="280" height="280">
<text
x="50%"
y="50%"
text-anchor="middle"
>
Hello World!
</text>
</svg>
Hello World!

Wenn man eine Form zeichnet, die bis zum Rahmen geht, wird der Overflow abgeschnitten:

<svg width="280" height="280">
<circle
class="filled"
cx="140"
cy="140"
r="140"
stroke="cornflowerblue"
stroke-width="5"
fill="none"
/>
</svg>

Das passiert, weil die Kontur von der Mitte des Pfades aus gezeichnet wird. Man könnte jetzt rechnen und die Form an die Grenzen des SVG anpassen, oder man macht den overflow mit CSS einfach sichtbar: overflow=“visible”. Bei SVGs wird der Overflow standardmäßig abgeschnitten, anders als in CSS.

<svg class="ovw" width="280" height="280">
<circle
class="filled"
cx="140"
cy="140"
r="140"
stroke="cornflowerblue"
stroke-width="5"
fill="none"
/>
</svg>

Standardmäßig endet ein Pfad mit einer geraden Linie. Wir können dieses Verhalten mit stroke-linecap kontrollieren (butt = default, round, square, wobei square einfach über die Kontur rausgeht):

<svg
width="220"
height="220"
stroke="limegreen"
stroke-width="20"
stroke-linecap="butt"
>
<line x1="40" y1="40" x2="180" y2="180" />
<line x1="180" y1="40" x2="40" y2="180" />
</svg>
<svg
width="220"
height="220"
stroke="limegreen"
stroke-width="20"
stroke-linecap="round"
>
<line x1="40" y1="40" x2="180" y2="180" />
<line x1="180" y1="40" x2="40" y2="180" />
</svg>
<svg
width="220"
height="220"
stroke="limegreen"
stroke-width="20"
stroke-linecap="square"
>
<line x1="40" y1="40" x2="180" y2="180" />
<line x1="180" y1="40" x2="40" y2="180" />
</svg>

Ähnlich ist stroke-linejoin. Damit kontrollieren wir, wie zwei Linien bei polygon oder polyline zusammenstoßen (miter = default, round, bevel). Miter kann oft sehr spitze Ecken machen, es wird dann automatisch zu bevel.

<div class="flex-wrap">
<svg
width="220"
height="220"
fill="none"
stroke="lime"
stroke-width="15"
stroke-linejoin="miter"
>
<polygon points="110,60 170,170 50,170" />
</svg>
<svg
width="220"
height="220"
fill="none"
stroke="lime"
stroke-width="15"
stroke-linejoin="miter"
>
<polyline points="60,160 160,160 89,89" />
</svg>
<svg
width="220"
height="220"
fill="none"
stroke="lime"
stroke-width="15"
stroke-linejoin="miter"
>
<polyline points="60,160 160,160 65,127" />
</svg>
</div>

Bevel ist unschön. Um unsere Winkel spitz zu halten, können wir stroke-miterlimit auf einen großen Wert setzen, z.B. 100.

Mit SVG Dashes haben wir sehr viel mehr Spielraum als mit CSS dashed und dotted, da auch die Länge der Zwischenräume kontrolliert werden kann: das macht stroke-dasharray. (Die Werte können mit Komma oder Leerzeichen getrennt werden.)

<svg class="animated" width="280" height="280">
<polyline
points="
60,60
80,260
200,260
220,60
"
fill="none"
stroke="hotpink"
stroke-width="5"
stroke-dasharray="10,10"
/>
</svg>

Dashes sind animierbar! Und wir können Dash-Muster auch wiederholen: dash - gap - dash -gap. Und um der Sache die Krone aufzusetzen, auch stroke-linecap funktioniert mit dasharray!

<div class="flex-wrap">
<svg width="200" height="200" fill="none">
<circle
cx="100"
cy="100"
r="40"
stroke="hotpink"
stroke-width="10"
stroke-dasharray="6,4,6,20"
/>
</svg>
<svg width="300" height="300">
<circle
cx="150"
cy="150"
r="100"
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="5"
stroke-dasharray="20,30"
stroke-linecap="round"
/>
</svg>
</div>

dash-offset … bewirkt, dass dieses Muster um den Pfad herumläuft. Das wird mit einem sehr großen gap interessant:

<svg width="276" height="276" fill="none">
<circle
cx="138"
cy="138"
r="87.54"
stroke="slateblue"
stroke-width="5"
stroke-dasharray="100,450"
stroke-dashoffset="0"
stroke-linecap="round"
/>
</svg>
<svg width="240" height="240">
<circle
r="120"
cx="120"
cy="120"
fill="hsl(45deg 100% 50%)"
/>
<ellipse
cx="80"
cy="100"
rx="15"
ry="20"
fill="hsl(35deg 80% 20%)"
/>
<ellipse
cx="160"
cy="100"
rx="15"
ry="20"
fill="hsl(35deg 80% 20%)"
/>
<circle
cx="120"
cy="120"
r="60"
fill="none"
stroke="hsl(35deg 80% 20%)"
stroke-width="10"
stroke-dasharray="100,450"
stroke-dashoffset="-45"
stroke-linecap="round"
/>
</svg>
<svg width="200" height="100">
<polyline
points=""
fill="none"
stroke="hotpink"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<svg width="64" height="64" viewBox="0 0 24 24" stroke="#000000" stroke-width="2" stroke-linecap="round">
<line x1="5" y1="7" x2="19" y2="7" />
<line x1="5" y1="12" x2="19" y2="12" />
<line x1="5" y1="17" x2="19" y2="17" />
</svg>
<svg width="300" height="200" viewBox="0 0 300 200">
<rect x="0" y="0" width="300" height="200" fill="gainsboro" />
<rect x="100" y="50" width="100" height="100" stroke-width="3" stroke="#FFFFFF" stroke-linecap="round" fill="none" />
<circle r="15" cx="127" cy="80" fill="#FFFFFF" />
<path d="
M 100,105
C 140,160
160,60
200,110
L 200,150
L 100,150
Z
"
stroke="#FFFFFF" stroke-width="3" fill="#FFFFFF" />
</svg>
<svg width="300" height="200" viewBox="0 0 300 200">
<rect x="0" y="0" width="300" height="200" fill="var(--highlight-color)"/>
<rect x="100" y="50" width="100" height="100" stroke-width="3" stroke="#FFFFFF" stroke-linecap="round" fill="none" />
<circle r="15" cx="127" cy="80" fill="#FFFFFF" />
<path d="
M 100,105
C 140,160
160,60
200,110
L 200,150
L 100,150
Z
"
stroke="#FFFFFF" stroke-width="3" fill="#FFFFFF" />
</svg>
<svg width="300" height="200" viewBox="0 0 300 200">
<rect x="0" y="0" width="300" height="200" />
<rect x="100" y="50" width="100" height="100" stroke-width="3" stroke="#FFFFFF" stroke-linecap="round" fill="none" />
<circle r="15" cx="127" cy="80" fill="#FFFFFF" />
<path d="
M 100,105
C 140,160
160,60
200,110
L 200,150
L 100,150
Z
"
stroke="#FFFFFF" stroke-width="3" fill="#FFFFFF" />
</svg>

Mit dem Attribut preserveAspectRatio können wir die fixen Proportionen (den default-Wert) aufheben. preserveAspectRatio="none" erlaubt, ein SVG zu strecken und zusammenzudrücken.

Während preserveAspectRatio für gefüllte Formen gut funktioniert, passiert bei Linien etwas seltsames: Eine geschwungene Linie wird beim auseinanderziehen bzw. zusammenquetschen an manchen Stellen dicker, an manchen dünner. Warum tut sie das? An den Stellen, an denen die Linie waagrecht oder fast waagrecht verläuft, wird sie beim zusammenquetschen dicker, wo sie (annähernd) senkrecht verläuft, wird sie dünner. Das macht Sinn – denn wenn die Breite reduziert wird, wird eben auch die Breite der vertikalen Linie im Verhältnis dünner. Wir können das mit CSS fixen:

svg * {
vector-effect: non-scaling-stroke;
}

Vermutlich verändert diese CSS-Eigenschaft die Reihenfolge, in der die Operationen innerhalb des SVG ausgeführt werden. Statt die Linie vor dem zusammenquetschen oder strecken zu zeichnen, wird sie nachher gezeichnet. So wird der Pfad verändert, aber nicht die Linie selbst.

<div class="swoops-wrapper">
<svg class="swoops"
preserveAspectRatio="none"
viewBox="0 0 780 151"
>
<path
d="M175 0.0551758C78 -1.94482 0 51.0553 0 51.0553V150.055H780V119.055C780 119.055 723 32.0551 585 38.0552C447 44.0553 429 84.0552 348 70.0553C267 56.0553 272 2.05518 175 0.0551758Z"
/>
</svg>
</div>