Proiectarea și implementarea unui editor SVG care rulează exclusiv în browser
Ideile principale ale articolului
- Am lucrat din nou cu ChatGPT — mai ales într-o postură de manager de proiect — pentru a construi editorul SVG (/ro/tools/svgedit/), iar aici notez toate locurile în care m-am împiedicat.
- Nu aș fi putut finaliza proiectul singur; inteligența artificială generativă strălucește cu adevărat la asistența de programare.
- În același timp, „să lași totul pe seama AI-ului” nu merge. A trebuit să coordonez designul general, să corectez neînțelegerile și să dirijez depanarea, exact ca un manager care joacă și pe teren.
- Din acest punct de vedere, experiența oferă indicii despre ce înseamnă să lucrezi cot la cot cu AI în lumea reală.
Proiectarea și implementarea unui editor SVG care rulează exclusiv în browser
Ce este SVG?
SVG (Scalable Vector Graphics) este un format de imagine vectorială bazat pe XML, standardizat de W3C. Spre deosebire de imaginile raster precum PNG sau JPEG, grafica SVG poate fi scalată fără pierderi de calitate. Pentru că un SVG poate fi încorporat direct într-un document HTML ca parte a DOM-ului, JavaScript și CSS îl pot manipula și stiliza dinamic. Specificația urmează recomandarea W3C pentru SVG și acceptă forme, text, trasee, degradeuri, filtre și multe altele.
SVG este folosit în numeroase contexte:
- Afișarea de pictograme și diagrame pe web
- Generarea de grafice și diagrame dinamice
- Construirea de aplicații de desen care reacționează la inputul utilizatorului
- Crearea de resurse pentru tipar sau design de interfață
Prezentare generală a editorului
Editorul descris aici este o unealtă complet client-side care rulează în browser. Utilizatorii pot desena nu doar dreptunghiuri și elipse, ci și stele, baloane de dialog, nori, inimioare și curbe liber trasate prin modul stilou. Formele pot fi mutate, redimensionate și rotite. Interfața include aliniere, ajustare la rețea, grupare și o listă de straturi. Lucrarea finală poate fi exportată ca SVG sau PNG.
Secțiunile de mai jos explică modul în care am transpus specificația SVG în JavaScript și toate punctele în care m-am împotmolit pe parcurs.
Implementarea specificației și obstacolele întâlnite
Adăugarea formelor și gestionarea atributelor
Specificația SVG definește elemente și atribute dedicate pentru fiecare tip de formă. Dreptunghiurile folosesc <rect>
, elipsele <ellipse>
, poligoanele <polygon>
, iar fiecare necesită setarea corectă a atributelor x
, y
, width
, height
, cx
, cy
, rx
, ry
sau points
. Formele mai complexe, precum stelele sau inimile, sunt create cu elemente <path>
ce combină curbe Bézier și comenzi de arc.
Exemplu de implementare (adăugarea unui dreptunghi)
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);
}
Dreptunghiurile și elipsele sunt simple, însă poligoanele și formele personalizate cer calcule de coordonate. Pentru a desena o stea, de exemplu, împarți unghiul central în mod egal și calculezi vârfurile înainte de a le scrie în atributul points
.
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);
}
Transformări și sisteme de coordonate
SVG îți permite să combini translate
, rotate
și scale
prin atributul transform
. Partea dificilă a fost obținerea chenarului de selecție al formei alese ținând cont de sistemele de coordonate locale și globale. Soluția a fost să apelez getScreenCTM()
pentru a converti totul în sistemul global înainte de a calcula dreptunghiul.
Aplicarea transformării de coordonate
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));
}
Cu asta la locul lui, cadrul de selecție rămâne corect chiar și după rotiri sau scalări.
Ajustarea la grilă
Grila este randată cu un <pattern>
SVG în fundal. Funcția de ajustare rotunjește coordonatele în JavaScript printr-un mic helper.
function snapToGrid(value, gridSize) {
return Math.round(value / gridSize) * gridSize;
}
Pentru ajustarea la alte obiecte, editorul scanează formele din jur pentru centre și puncte extreme, găsește cel mai apropiat candidat și afișează ghiduri pentru a „magnetiza” forma selectată. Acest lucru a îmbunătățit radical precizia interfeței.
Editarea textului
Deoarece un element <text>
SVG nu poate fi editat direct, editorul suprapune temporar un <input>
HTML pentru editare în linie.
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);
});
}
Trasee și curbe libere
Elementul <path>
folosește comenzi precum M, L, C și A. Am început cu editare pe linii drepte, unde fiecare clic adaugă o nouă coordonată.
let pathData = "M100,100";
function addPathPoint(x, y) {
pathData += ` L${x},${y}`;
path.setAttribute("d", pathData);
}
Export ca SVG sau PNG
La salvare, editorul elimină elementele auxiliare și apoi exportă. Conversia în PNG trece printr-un 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;
}
Gestionarea canvasurilor derulabile
Pentru planșe mari a fost suficient să înfășor SVG-ul într-un <div style="overflow:auto">
și să las barele de derulare să apară la nevoie pentru a păstra interfața utilizabilă.
Concluzie
Specificația SVG este puternică, dar implementarea ei înseamnă să te lupți cu transformări de coordonate, definiții de trasee și multe altele. Acest editor organizează toate aceste aspecte și oferă următoarele funcții:
- Gestionarea atributelor pentru dreptunghiuri, elipse, poligoane și trasee
- Utilizarea corectă a
transform
și a sistemelor de coordonate - Suport pentru grilă și ajustare la rețea
- Editare de text în linie
- Editare de trasee (deocamdată segmente de linie)
- Export atât în PNG, cât și în SVG
- Suport pentru planșe mari cu derulare
Valorificarea expresivității SVG a făcut posibilă construirea unui editor performant care rulează integral în browser.