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>
<!-- 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>
</body>

View File

@ -1,50 +1,68 @@
// js/canvasManager.js
let backgroundCanvas;
let lastShapesArray = [];
const SVG_NS = "http://www.w3.org/2000/svg";
/**
* Zeichnet alle Shapes auf dem Canvas neu
* @param {HTMLCanvasElement} canvas
* @param {Array} shapesArray - Array der Shapes aus dem Dokument
* Renders all shapes into an SVG container
* @param {SVGElement} container
* @param {Array} shapesArray
* @param {string|null} movingShapeId
*/
export function drawAllShapesOnCanvas(canvas, shapesArray) {
const ctx = canvas.getContext("2d");
// Canvas leerräumen
ctx.clearRect(0, 0, canvas.width, canvas.height);
export function drawAllShapesOnCanvas(container, shapesArray, movingShapeId = null) {
if (!container || !(container instanceof SVGElement)) {
return;
}
// Create or update shapes
shapesArray.forEach(shape => {
if (shape.type === "circle") {
drawCircle(ctx, shape);
} else if (shape.type === "triangle") {
drawTriangle(ctx, shape);
}
let element = document.getElementById(shape.id);
if (!element) {
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");
}
});
}
/**
* Zeichnet einen Kreis
* @param {CanvasRenderingContext2D} ctx
* @param {Object} circle
*/
function drawCircle(ctx, circle) {
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();
}
// Only clean up if we have shapes to compare against
if (shapesArray && Array.isArray(shapesArray)) {
const currentIds = shapesArray.map(shape => String(shape.id));
Array.from(container.children).forEach(element => {
if (!currentIds.includes(element.id)) {
container.removeChild(element);
}
});
}
}

View File

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