Kurvige Pfade direkt in CSS, ohne einen einzigen SVG-Pfad-String zu schreiben
Die shape()-Funktion erweitert clip-path um echte Kurvenbefehle wie curve to, arc to und smooth to, direkt in lesbarer CSS-Syntax. Wo bisher nur ein externes SVG oder eine grobe Polygon-Annäherung half, entstehen jetzt organische, kurvige Formen ohne Build-Step und ohne Pfad-Editor.
Inhaltsverzeichnis
- 1. Was die shape()-Funktion ist und welches Problem sie löst
- 2. Grundsyntax: from, line to, curve to und close
- 3. shape() vs. polygon(): wann welches Werkzeug
- 4. Organische Formen bauen: mehrere curve-to-Befehle kombinieren
- 5. arc to: echte Kreisbögen ohne Bézier-Annäherung
- 6. shape() animieren: weiche Formübergänge ohne JavaScript
- 7. Browser-Unterstützung und Fallback auf polygon()
- 8. Praktischer Workflow: DevTools-Editor, Design-Tools und Wartbarkeit
- 9. Praxis-Einsatz: Kampagnenflächen, Badges und dekorative Trenner
- 10. Zusammenfassung
- 11. FAQ
1. Was die shape()-Funktion ist und welches Problem sie löst
Bis vor kurzem kannte clip-path im Wesentlichen zwei Wege, um eine Form zu beschreiben: gerade Linien über polygon() oder eine komplette, extern eingebundene SVG-Datei mit einem <clipPath>-Element. Für alles, was Kurven, Bögen oder weiche Übergänge braucht, blieb bisher nur der Umweg über SVG, oft erzeugt in einem Grafikprogramm und dann als Pfad-String kopiert, der für Menschen kaum lesbar und noch schwerer von Hand anzupassen war.
shape() schließt genau diese Lücke, indem es echte Kurvenbefehle direkt als CSS-Funktionswert einführt. Statt eines kryptischen SVG-d-Attributs wie M10 10 C 20 20, 40 20, 50 10 steht jetzt eine sprechende Syntax wie curve to 50px 10px with 20px 20px zur Verfügung, die sich direkt im Stylesheet liest, versteht und mit CSS-Werten wie calc() oder Custom Properties kombinieren lässt, was bei einem SVG-Pfad-String schlicht nicht möglich ist.
2. Grundsyntax: from, line to, curve to und close
Eine shape()-Deklaration beginnt immer mit from <startpunkt> und listet danach eine Folge von Befehlen auf, die von diesem Punkt aus einen Pfad zeichnen. line to zeichnet eine gerade Linie zum angegebenen Punkt, genau wie ein Eckpunkt in polygon(), während curve to <zielpunkt> with <kontrollpunkt> eine quadratische oder mit zwei Kontrollpunkten eine kubische Bézierkurve zeichnet. Der Befehl close am Ende schließt den Pfad zurück zum Startpunkt, exakt wie Z in SVG.
Alle Koordinaten akzeptieren die üblichen CSS-Längeneinheiten, Prozentwerte relativ zur Referenzbox des Elements und sogar calc()-Ausdrücke, wodurch sich eine Form mit einer festen Randbreite von zum Beispiel 20 Pixel unabhängig von der tatsächlichen Elementgröße beschreiben lässt. Das ist ein Vorteil, den ein statischer SVG-Pfad-String von Natur aus nicht bietet, weil dessen Koordinaten beim Export fest eingebrannt werden.
/* Ein Dreieck mit einer abgerundeten Spitze */
.rounded-triangle {
clip-path: shape(
from 50% 0%,
line to 100% 100%,
curve to 0% 100% with 50% 130%,
close
);
}
3. shape() vs. polygon(): wann welches Werkzeug
polygon() bleibt die richtige Wahl für alles, was tatsächlich aus geraden Kanten besteht, etwa ein Chevron, ein Pfeil oder ein schräg abgeschnittener Hero-Bereich. Die Syntax ist kompakt, jeder Punkt ist eine simple Koordinate, und es gibt keinen Grund, dafür Kurvenbefehle zu bemühen, die am Ende doch nur eine Gerade beschreiben würden.
Sobald aber eine Kante tatsächlich rund oder organisch wirken soll, zeigt sich der Unterschied deutlich: Eine runde Form mit polygon() nachzubilden bedeutet, sie in viele kleine gerade Segmente zu zerlegen, was entweder kantig aussieht oder eine große Zahl an Koordinatenpaaren erfordert, die kaum noch von Hand zu pflegen sind. shape() beschreibt dieselbe Rundung mit einem einzigen curve to- oder arc to-Befehl, bei exakt gleichem Rendering-Ergebnis, aber lesbarer Quelle.
4. Organische Formen bauen: mehrere curve-to-Befehle kombinieren
Für eine frei geformte, blob-artige Fläche reihen sich mehrere curve to-Befehle aneinander, wobei jeder Kontrollpunkt bestimmt, wie stark die Kurve in diesem Abschnitt nach außen oder innen ausbaucht. Der praktische Arbeitsablauf ist meist iterativ: grobe Eckpunkte wie bei einem Polygon festlegen, dann Stück für Stück line to durch curve to ersetzen und die Kontrollpunkte im Browser-DevTools-Formen-Editor live verschieben, bis die Silhouette organisch wirkt.
Ein Vorteil gegenüber einem SVG-Export aus einem Design-Tool ist, dass jeder einzelne Punkt und jede Kurve im Quellcode benannt und nachvollziehbar bleibt. Wer später nur die obere Kante anpassen will, ändert genau den betroffenen curve to-Befehl, statt einen kompletten, maschinengenerierten Pfad-String zu durchsuchen und das richtige Zahlenpaar darin zu erraten.
/* Organische Blob-Form aus vier Kurvensegmenten */
.blob {
clip-path: shape(
from 20% 0%,
curve to 100% 35% with 70% 0%,
curve to 75% 100% with 100% 70%,
curve to 0% 65% with 30% 100%,
curve to 20% 0% with 0% 30%,
close
);
}
5. arc to: echte Kreisbögen ohne Bézier-Annäherung
Neben curve to für Bézierkurven bietet shape() mit arc to einen eigenen Befehl für echte Kreis- und Ellipsenbögen, angegeben über Zielpunkt, Radius und Richtung (cw für im Uhrzeigersinn, ccw für gegen den Uhrzeigersinn). Das ist der entscheidende Unterschied zu einer Bézierkurve, die einen Kreis immer nur annähert: Ein arc to-Bogen ist geometrisch exakt, was bei abgerundeten Ecken oder kreisförmigen Aussparungen sichtbar glattere Ergebnisse liefert als eine grob abgeschätzte Kurve.
Damit lassen sich zum Beispiel abgerundete Rechtecke mit exakt definiertem Eckenradius bauen, ganz ohne border-radius, was immer dann nützlich ist, wenn die Form Teil eines größeren clip-path-Konstrukts ist und border-radius allein nicht ausreicht, weil zusätzlich eine gerade Kante an anderer Stelle abgeschnitten werden muss.
/* Abgerundetes Rechteck ueber vier arc-to-Befehle */
.rounded-cutout {
clip-path: shape(
from 0 20px,
arc to 20px 0 of 20px cw,
line to calc(100% - 20px) 0,
arc to 100% 20px of 20px cw,
line to 100% calc(100% - 20px),
arc to calc(100% - 20px) 100% of 20px cw,
line to 20px 100%,
arc to 0 calc(100% - 20px) of 20px cw,
close
);
}
6. shape() animieren: weiche Formübergänge ohne JavaScript
Weil zwei shape()-Werte mit der gleichen Anzahl und Reihenfolge an Befehlen interpolierbar sind, kann der Browser zwischen ihnen sauber animieren, etwa bei einem Hover-Zustand, der eine Blob-Form leicht verzerrt, oder einem Scroll-getriebenen Übergang von einer runden zu einer eckigen Silhouette. Vorausgesetzt ist lediglich, dass Start- und Zielform strukturell zueinander passen, also dieselbe Zahl an curve to- oder arc to-Befehlen in derselben Reihenfolge verwenden.
Das ist ein klarer Vorteil gegenüber der Animation eines SVG-path-Attributs, das ohne zusätzliche JavaScript-Bibliothek wie GSAP kaum sinnvoll interpolierbar ist, weil der Browser zwei beliebige Pfad-Strings nicht automatisch aufeinander abbildet. Mit shape() reicht eine einfache CSS-transition-Deklaration auf clip-path, und der Browser übernimmt die komplette Interpolation der einzelnen Koordinaten.
.blob-interactive {
clip-path: shape(from 20% 0%, curve to 100% 35% with 70% 0%,
curve to 75% 100% with 100% 70%, curve to 0% 65% with 30% 100%,
curve to 20% 0% with 0% 30%, close);
transition: clip-path 0.4s ease;
}
.blob-interactive:hover {
clip-path: shape(from 15% 0%, curve to 100% 25% with 80% 0%,
curve to 80% 100% with 100% 80%, curve to 0% 75% with 20% 100%,
curve to 15% 0% with 0% 20%, close);
}
7. Browser-Unterstützung und Fallback auf polygon()
shape() ist neuer als polygon() und die Unterstützung in aktuellen Browsern wächst, ist aber noch nicht so flächendeckend wie bei den etablierten clip-path-Funktionen. Der pragmatische Umgang damit ist ein zweistufiges Fallback: Zunächst eine grobe polygon()-Näherung deklarieren, die in jedem Browser funktioniert, und direkt danach die feinere shape()-Version, die in Browsern mit Unterstützung die vorherige Deklaration überschreibt.
Eine @supports-Abfrage macht diese Absicherung explizit und verhindert, dass in älteren Browsern eine komplett eckige oder gar keine Form angezeigt wird. Wichtig ist dabei, dass die polygon()-Näherung optisch nah genug an der Zielform liegt, damit der Unterschied für Nutzer ohne shape()-Unterstützung nicht als Bruch wahrgenommen wird, sondern höchstens als etwas kantigere Variante derselben Form.
.blob {
/* Fallback: grobe Polygon-Naeherung fuer aeltere Browser */
clip-path: polygon(20% 0%, 65% 5%, 100% 35%, 90% 70%, 75% 100%, 35% 95%, 0% 65%, 5% 25%);
}
@supports (clip-path: shape(from 0 0, line to 100% 100%)) {
.blob {
clip-path: shape(from 20% 0%, curve to 100% 35% with 70% 0%,
curve to 75% 100% with 100% 70%, curve to 0% 65% with 30% 100%,
curve to 20% 0% with 0% 30%, close);
}
}
8. Praktischer Workflow: DevTools-Editor, Design-Tools und Wartbarkeit
Aktuelle Browser-DevTools bieten mittlerweile einen visuellen Editor für clip-path-Werte, der auch shape()-Pfade direkt im Canvas anfasst und verschiebt, ähnlich wie ein Vektor-Werkzeug in einem Design-Programm. Das beschleunigt die Feinabstimmung erheblich, weil Kontrollpunkte per Maus statt durch wiederholtes Ändern von Zahlenwerten im Editor angepasst werden können, während das Ergebnis sofort als lesbarer CSS-Wert im Stylesheet landet.
Gegenüber einem in Figma oder Illustrator exportierten SVG-Pfad bleibt shape() außerdem Teil des CSS-Build-Prozesses, ohne zusätzliche Asset-Datei, zusätzlichen HTTP-Request oder eine separate Pflege zwischen Design-Tool und Repository. Für ein Team, das Formen häufiger anpasst, etwa bei saisonalen Kampagnenseiten, reduziert das die Reibung zwischen Design und Umsetzung spürbar.
9. Praxis-Einsatz: Kampagnenflächen, Badges und dekorative Trenner
Im Shop-Kontext eignet sich shape() besonders für saisonale Kampagnenflächen mit organischer Silhouette, für Badge- und Sticker-Elemente mit weichen statt scharfen Ecken, und für dekorative Section-Trenner, die einen sanften Wellenverlauf statt einer harten Kante zwischen zwei Bereichen zeichnen. In allen drei Fällen ersetzt eine einzige shape()-Deklaration ein zusätzliches SVG-Asset, das sonst separat gehostet, geladen und bei jeder Design-Anpassung neu exportiert werden müsste.
Wichtig bleibt auch hier, die Form als rein dekoratives Element zu behandeln: Interaktive Bereiche wie Buttons oder Links sollten niemals ausschließlich über eine shape()-Kontur klickbar gemacht werden, ohne die zugrunde liegende Klickfläche für Tastatur- und Screenreader-Nutzung ausreichend groß zu halten.
| Funktion | Kurven möglich | Lesbarkeit | Typischer Einsatz |
|---|---|---|---|
polygon() |
Nein, nur gerade Kanten | Hoch, einfache Koordinaten | Chevrons, Pfeile, schräge Sections |
shape() mit curve to |
Ja, Bézierkurven | Hoch, benannte Befehle | Organische Blobs, weiche Kanten |
shape() mit arc to |
Ja, echte Kreisbögen | Hoch, benannte Befehle | Präzise Rundungen, Kreisausschnitte |
Externes SVG clipPath |
Ja, beliebig komplex | Niedrig, generierter Pfad-String | Sehr komplexe, statische Illustrationen |
Mironsoft
Modernes CSS, Layout-Architektur und Rendering-Performance
CSS, das wartbar bleibt statt mit jeder Änderung zu brechen?
Wir prüfen bestehende Stylesheets auf Spezifitäts-Chaos und Layout-Thrashing und bauen daraus eine CSS-Architektur mit Cascade Layers, Custom Properties und modernen Layout-Primitiven, die auch nach dem zehnten Feature noch verständlich ist.
CSS-Audit
Spezifität, Cascade-Konflikte und ungenutzte Selektoren systematisch aufdecken.
Architektur-Refactoring
Cascade Layers, Custom Properties und Design Tokens sauber einführen.
Performance-Tuning
Layout-Thrashing, teure Selektoren und Rendering-Engpässe gezielt beheben.
10. Zusammenfassung
clip-path shape() in CSS: Das Wichtigste auf einen Blick
Grundprinzip
shape() bringt echte Kurvenbefehle wie curve to und arc to direkt in clip-path, ohne Umweg über einen externen SVG-Pfad-String.
Vs. polygon()
polygon() bleibt für gerade Kanten die einfachere Wahl, shape() lohnt sich, sobald eine Kante tatsächlich rund oder organisch wirken soll.
Animierbar
Zwei strukturell gleiche shape()-Werte interpolieren der Browser automatisch, eine reine CSS-transition genügt für weiche Formübergänge.
Fallback
Eine polygon()-Näherung vor der shape()-Deklaration und eine @supports-Abfrage sichern ältere Browser sauber ab.