Replaced canvas by SVG objects allowing direct DOM manipulation

This commit is contained in:
Stephan Egli 2025-01-10 14:43:24 +01:00
parent 834e19d9b8
commit dcd471e2be
3 changed files with 95 additions and 67 deletions

View File

@ -17,7 +17,11 @@
</div> </div>
<!-- Canvas, auf dem wir die Formen zeichnen --> <!-- Canvas, auf dem wir die Formen zeichnen -->
<canvas id="shapesCanvas" width="600" height="400"></canvas> <svg id="shapesCanvas" width="800" height="600"
style="border: 1px solid black;"
xmlns="http://www.w3.org/2000/svg"
version="1.1">
</svg>
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
</body> </body>

View File

@ -1,50 +1,68 @@
// js/canvasManager.js // js/canvasManager.js
let backgroundCanvas;
let lastShapesArray = [];
const SVG_NS = "http://www.w3.org/2000/svg";
/** /**
* Zeichnet alle Shapes auf dem Canvas neu * Renders all shapes into an SVG container
* @param {HTMLCanvasElement} canvas * @param {SVGElement} container
* @param {Array} shapesArray - Array der Shapes aus dem Dokument * @param {Array} shapesArray
* @param {string|null} movingShapeId
*/ */
export function drawAllShapesOnCanvas(canvas, shapesArray) { export function drawAllShapesOnCanvas(container, shapesArray, movingShapeId = null) {
const ctx = canvas.getContext("2d"); if (!container || !(container instanceof SVGElement)) {
return;
// Canvas leerräumen }
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Create or update shapes
shapesArray.forEach(shape => { shapesArray.forEach(shape => {
if (shape.type === "circle") { let element = document.getElementById(shape.id);
drawCircle(ctx, shape);
} else if (shape.type === "triangle") { if (!element) {
drawTriangle(ctx, shape); if (shape.type === "circle") {
} element = document.createElementNS(SVG_NS, "circle");
element.id = shape.id;
container.appendChild(element);
} else if (shape.type === "triangle") {
element = document.createElementNS(SVG_NS, "polygon");
element.id = shape.id;
container.appendChild(element);
}
}
if (!element) {
return;
}
// Update element attributes using setAttributeNS for SVG
if (shape.type === "circle") {
element.setAttributeNS(null, "cx", shape.x);
element.setAttributeNS(null, "cy", shape.y);
element.setAttributeNS(null, "r", shape.radius);
element.setAttributeNS(null, "fill", shape.color || "black");
element.setAttributeNS(null, "stroke", "black");
element.setAttributeNS(null, "stroke-width", "1");
} else if (shape.type === "triangle") {
const points = shape.coordinates
.map(p => `${p.x},${p.y}`)
.join(" ");
element.setAttributeNS(null, "points", points);
element.setAttributeNS(null, "fill", shape.color || "black");
element.setAttributeNS(null, "stroke", "black");
element.setAttributeNS(null, "stroke-width", "1");
}
}); });
}
/** // Only clean up if we have shapes to compare against
* Zeichnet einen Kreis if (shapesArray && Array.isArray(shapesArray)) {
* @param {CanvasRenderingContext2D} ctx const currentIds = shapesArray.map(shape => String(shape.id));
* @param {Object} circle Array.from(container.children).forEach(element => {
*/ if (!currentIds.includes(element.id)) {
function drawCircle(ctx, circle) { container.removeChild(element);
ctx.beginPath(); }
ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI); });
ctx.fillStyle = circle.color || "black"; }
ctx.fill(); }
}
/**
* Zeichnet ein Dreieck
* @param {CanvasRenderingContext2D} ctx
* @param {Object} triangle
*/
function drawTriangle(ctx, triangle) {
const [p1, p2, p3] = triangle.coordinates;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.lineTo(p3.x, p3.y);
ctx.closePath();
ctx.fillStyle = triangle.color || "black";
ctx.fill();
}

View File

@ -7,7 +7,7 @@ let selectedShapeId = null;
let offsetX = 0; let offsetX = 0;
let offsetY = 0; let offsetY = 0;
const canvas = document.getElementById("shapesCanvas") const container = document.getElementById("shapesCanvas");
// Add throttle function at the top // Add throttle function at the top
function throttle(func, limit) { function throttle(func, limit) {
@ -26,13 +26,16 @@ const handle = initRepo()
await handle.whenReady() await handle.whenReady()
// draw current state // draw current state
drawAllShapesOnCanvas(canvas, handle.docSync().shapes) drawAllShapesOnCanvas(container, handle.docSync().shapes)
// 2. Subscribe so we can re-render whenever doc changes // 2. Subscribe so we can re-render whenever doc changes
// Listen for 'change' events // Listen for 'change' events
handle.on("change", ({doc}) => { handle.on("change", ({doc}) => {
console.log("Document changed! Drawallshapes", doc) console.log("Document changed! Drawallshapes", doc);
drawAllShapesOnCanvas(canvas, doc.shapes) // Only redraw all if we're not currently dragging
if (!isDragging) {
drawAllShapesOnCanvas(container, doc.shapes);
}
}) })
// 3. Buttons holen // 3. Buttons holen
@ -51,44 +54,47 @@ addTriangleButton.addEventListener("click", () => {
}); });
// Mousedown: Prüfen, ob wir auf ein Shape klicken // Mousedown: Prüfen, ob wir auf ein Shape klicken
canvas.addEventListener("mousedown", (e) => { container.addEventListener("mousedown", (e) => {
const { offsetX: mouseX, offsetY: mouseY } = e; // Get SVG coordinates
const pt = container.createSVGPoint();
pt.x = e.clientX;
pt.y = e.clientY;
const svgP = pt.matrixTransform(container.getScreenCTM().inverse());
// 1. Shape unter Maus suchen const clickedShape = getShapeAtCoordinates(svgP.x, svgP.y);
const clickedShape = getShapeAtCoordinates(mouseX, mouseY);
console.log("Clicked shape:",clickedShape)
if (clickedShape) { if (clickedShape) {
isDragging = true; isDragging = true;
selectedShapeId = clickedShape.id; selectedShapeId = clickedShape.id;
offsetX = svgP.x - clickedShape.x;
// Damit das Shape unter dem Mauszeiger bleibt, merken wir uns den "Offset" offsetY = svgP.y - clickedShape.y;
offsetX = mouseX - clickedShape.x;
offsetY = mouseY - clickedShape.y;
} }
}); });
// Throttled version of moveShape // Throttled version of moveShape
const throttledMoveShape = throttle((shapeId, x, y) => { const throttledMoveShape = throttle((shapeId, x, y) => {
moveShape(shapeId, x, y); moveShape(shapeId, x, y);
// Pass the moving shape ID to drawAllShapesOnCanvas
drawAllShapesOnCanvas(container, handle.docSync().shapes, shapeId);
}, 16); // 50ms throttle time (20 updates per second) }, 16); // 50ms throttle time (20 updates per second)
// Mousemove: Shape verschieben, sofern dragging aktiv ist // Mousemove: Shape verschieben, sofern dragging aktiv ist
canvas.addEventListener("mousemove", (e) => { container.addEventListener("mousemove", (e) => {
if (!isDragging || selectedShapeId === null) return; if (!isDragging || selectedShapeId === null) return;
const { offsetX: mouseX, offsetY: mouseY } = e; const pt = container.createSVGPoint();
pt.x = e.clientX;
pt.y = e.clientY;
const svgP = pt.matrixTransform(container.getScreenCTM().inverse());
// Neue Koordinaten berechnen const newX = svgP.x - offsetX;
const newX = mouseX - offsetX; const newY = svgP.y - offsetY;
const newY = mouseY - offsetY;
// Use throttled version instead
throttledMoveShape(selectedShapeId, newX, newY); throttledMoveShape(selectedShapeId, newX, newY);
}); });
// Mouseup: Dragging beenden // Mouseup: Dragging beenden
canvas.addEventListener("mouseup", () => { container.addEventListener("mouseup", () => {
isDragging = false; isDragging = false;
selectedShapeId = null; selectedShapeId = null;
}); });