Kernaussagen dieses Artikels

  • Gemeinsam mit ChatGPT – de facto in einer Projektleiterrolle – wollte ich einen SVG-Editor (/de/tools/svgedit/) bauen. Hier notiere ich alle Stolpersteine.
  • Alleine hätte ich es nie geschafft. Die Coding-Fähigkeiten generativer KI sind weiterhin beeindruckend.
  • Gleichzeitig funktioniert „einfach alles der KI überlassen“ nicht. Man muss das Gesamtbild koordinieren, Fehlannahmen korrigieren und die Störungsbehebung steuern – also exakt die Rolle einer spielenden Führungskraft.
  • In diesem Sinne liefert das Projekt auch Hinweise darauf, wie wir im KI-Zeitalter arbeiten müssen.

Einen reinen Browser-SVG-Editor entwickeln: Umsetzung und Spezifikation erklärt

Was ist SVG?

SVG (Scalable Vector Graphics) ist ein XML-basiertes Vektorformat, das vom W3C standardisiert wurde. Anders als Rastergrafiken (PNG, JPEG usw.) verliert es beim Skalieren keine Qualität. Weil SVG direkt als Teil des DOM in HTML eingebettet werden kann, lassen sich Grafiken mit JavaScript und CSS dynamisch manipulieren. Die Spezifikation folgt der W3C-SVG-Empfehlung und deckt Formen, Text, Pfade, Verläufe, Filter und vieles mehr ab.

SVG wird unter anderem hier eingesetzt:

  • Anzeigen von Icons und Diagrammen auf Webseiten
  • Dynamisches Erzeugen von Diagrammen und Charts
  • Zeichenanwendungen mit Benutzerinteraktion
  • Erstellung von Assets für Print und UI-Design

Überblick über den Editor

Der hier beschriebene Editor ist ein vollständig clientseitiges Tool, das im Browser läuft. Nutzende können nicht nur Rechtecke und Ellipsen zeichnen, sondern auch Sterne, Sprechblasen, Wolkenformen, Herzen sowie freie Kurven im Stiftmodus. Jede Form lässt sich verschieben, skalieren und drehen; Ausrichtung, Snapping, Gruppierung und eine Ebenenliste werden ebenfalls unterstützt. Fertige Grafiken lassen sich als SVG oder PNG exportieren.

Im Folgenden erläutere ich, wie die SVG-Spezifikation in JavaScript übersetzt wurde – und wo ich ins Stolpern geraten bin.

Spezifikation umsetzen und Fallstricke überwinden

Formen hinzufügen und Attribute verwalten

Die SVG-Spezifikation definiert für jede Form eigene Elemente und Attribute. Rechtecke nutzen <rect>, Ellipsen <ellipse>, Polygone <polygon>; die Attribute x, y, width, height, cx, cy, rx, ry oder points müssen korrekt gesetzt werden. Komplexere Formen wie Sterne oder Herzen entstehen über <path>-Elemente, die Bézierkurven und Bogenbefehle kombinieren.

Implementierungsbeispiel (Rechteck hinzufügen)

function addRectangle(x, y, width, height) {
  const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
  rect.setAttribute("x", x);
  rect.setAttribute("y", y);
  rect.setAttribute("width", width);
  rect.setAttribute("height", height);
  rect.setAttribute("fill", "transparent");
  rect.setAttribute("stroke", "#000");
  svg.appendChild(rect);
}

Rechtecke und Ellipsen sind simpel, doch Polygone und Sonderformen benötigen Koordinatenberechnungen. Für Sterne wird der Zentralwinkel gleichmäßig geteilt; die Spitzen wandern in das points-Attribut.

function createStar(cx, cy, spikes, outerR, innerR) {
  let points = "";
  const step = Math.PI / spikes;
  for (let i = 0; i < 2 * spikes; i++) {
    const r = i % 2 === 0 ? outerR : innerR;
    const x = cx + r * Math.cos(i * step);
    const y = cy + r * Math.sin(i * step);
    points += `${x},${y} `;
  }
  const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
  polygon.setAttribute("points", points.trim());
  polygon.setAttribute("stroke", "#000");
  polygon.setAttribute("fill", "transparent");
  svg.appendChild(polygon);
}

Transformationen und Koordinatensysteme

Über das transform-Attribut kombiniert SVG translate, rotate und scale. Die Herausforderung bestand darin, das Bounding-Box-Rechteck einer ausgewählten Form zu berechnen und dabei zwischen lokalem und globalem Koordinatensystem zu wechseln. Die Lösung war getScreenCTM(), um alles zunächst in globale Koordinaten zu transformieren.

Koordinatentransformation anwenden

function getTransformedBBox(element) {
  const bbox = element.getBBox();
  const matrix = element.getScreenCTM();
  const points = [
    svg.createSVGPoint(), svg.createSVGPoint(),
    svg.createSVGPoint(), svg.createSVGPoint()
  ];
  points[0].x = bbox.x; points[0].y = bbox.y;
  points[1].x = bbox.x + bbox.width; points[1].y = bbox.y;
  points[2].x = bbox.x; points[2].y = bbox.y + bbox.height;
  points[3].x = bbox.x + bbox.width; points[3].y = bbox.y + bbox.height;
  return points.map(p => p.matrixTransform(matrix));
}

Damit bleibt der Auswahlrahmen auch nach Drehung oder Skalierung korrekt.

Snapping und Raster

Das Raster wird über ein <pattern> im Hintergrund gezeichnet. Beim Snapping runden wir Koordinaten im JavaScript-Code schlicht auf das nächstgelegene Raster.

function snapToGrid(value, gridSize) {
  return Math.round(value / gridSize) * gridSize;
}

Für das Objekt-Snapping scannt der Editor die Zentren und Endpunkte anderer Formen, ermittelt den nächsten Kandidaten und blendet Hilfslinien ein, sodass die aktuelle Form „anzieht“. Das erhöht die Präzision der UI erheblich.

Texte bearbeiten

Ein SVG-<text>-Element lässt sich nicht direkt bearbeiten, daher legt der Editor temporär ein HTML-<input> darüber.

function editTextElement(textElem) {
  const input = document.createElement("input");
  input.type = "text";
  input.value = textElem.textContent;
  document.body.appendChild(input);
  const bbox = textElem.getBoundingClientRect();
  input.style.position = "absolute";
  input.style.left = bbox.x + "px";
  input.style.top = bbox.y + "px";
  input.focus();
  input.addEventListener("blur", () => {
    textElem.textContent = input.value;
    document.body.removeChild(input);
  });
}

Pfade und Freihandkurven

SVG-<path> kombiniert Befehle wie M, L, C oder A. Wir begannen mit linienbasiertem Editing: Jeder Klick fügt Koordinaten hinzu.

let pathData = "M100,100";
function addPathPoint(x, y) {
  pathData += ` L${x},${y}`;
  path.setAttribute("d", pathData);
}

Export (SVG/PNG)

Beim Speichern entfernen wir Hilfselemente. Die PNG-Konvertierung erfolgt über ein Canvas.

function exportToPNG(svgElement) {
  const serializer = new XMLSerializer();
  const source = serializer.serializeToString(svgElement);
  const img = new Image();
  const svgBlob = new Blob([source], { type: "image/svg+xml;charset=utf-8" });
  const url = URL.createObjectURL(svgBlob);
  img.onload = () => {
    const canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    const ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);
    canvas.toBlob(blob => {
      const a = document.createElement("a");
      a.href = URL.createObjectURL(blob);
      a.download = "drawing.png";
      a.click();
    });
  };
  img.src = url;
}

Scrollbarer Arbeitsbereich

Für große SVGs hilft ein <div style="overflow:auto">; so erscheinen bei Bedarf Scrollbalken.

Fazit

Die SVG-Spezifikation ist mächtig, in der Umsetzung aber herausfordernd – etwa bei Koordinatentransformationen oder Pfaddefinitionen. Im Editor konnten wir diese Hürden strukturieren und Folgendes erreichen:

  • Attributsteuerung für Formen (rect, ellipse, polygon, path)
  • Korrekte Handhabung von transform und Koordinatensystemen
  • Raster- und Snapping-Funktionen
  • Inline-Textbearbeitung
  • Pfadbearbeitung (linienbasiert)
  • Export als PNG oder SVG
  • Scrollbarer Canvas für große Szenen

So lässt sich die Ausdrucksstärke von SVG voll ausschöpfen und ein leistungsfähiger Editor bauen, der ausschließlich im Browser läuft.