diff --git a/projects/drawing-canvas/index.html b/projects/drawing-canvas/index.html index 1339431..2b2204e 100644 --- a/projects/drawing-canvas/index.html +++ b/projects/drawing-canvas/index.html @@ -1,4 +1,4 @@ - + @@ -10,9 +10,31 @@

Drawing Canvas

+
+ +
+ + + +

Note: Resizing the window will clear your drawing.

+
- + \ No newline at end of file diff --git a/projects/drawing-canvas/main.js b/projects/drawing-canvas/main.js index 3548de7..ed76a79 100644 --- a/projects/drawing-canvas/main.js +++ b/projects/drawing-canvas/main.js @@ -1,12 +1,160 @@ -/** - * TODO: Drawing Canvas - * - Initialize , handle mouse/touch drawing - * - Color picker, brush size, clear canvas - * - Save canvas as image (toDataURL) - * Optional: Eraser mode, undo/redo, pressure sensitivity (Pointer Events) - */ - -document.addEventListener('DOMContentLoaded', () => { + document.addEventListener('DOMContentLoaded', () => { console.log('Drawing Canvas ready'); + // TODO: Canvas setup and draw handlers -}); + const canvas = document.getElementById('drawingCanvas'); + const ctx = canvas.getContext('2d'); + const colorPicker = document.getElementById('colorPicker'); + const brushSize = document.getElementById('brushSize'); + const brushSizeValue = document.getElementById('brushSizeValue'); + const clearButton = document.getElementById('clearButton'); + const saveButton = document.getElementById('saveButton'); + + let isDrawing = false; + let lastX = 0; + let lastY = 0; + let dpr = window.devicePixelRatio || 1; + + // Initialize canvas + function initCanvas() { + const container = canvas.parentElement; + const width = container.clientWidth; + const height = container.clientHeight; + + // Set canvas size with DPR scaling + canvas.width = width * dpr; + canvas.height = height * dpr; + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + + // Scale context for high DPI displays + ctx.scale(dpr, dpr); + + // Set initial drawing styles + ctx.strokeStyle = colorPicker.value; + ctx.lineWidth = brushSize.value; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + + // Clear canvas with white background + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, width, height); + } + + // Drawing functions + function startDrawing(e) { + isDrawing = true; + const pos = getPointerPos(e); + [lastX, lastY] = [pos.x, pos.y]; + } + + function draw(e) { + if (!isDrawing) return; + + e.preventDefault(); + const pos = getPointerPos(e); + + ctx.beginPath(); + ctx.moveTo(lastX, lastY); + ctx.lineTo(pos.x, pos.y); + ctx.stroke(); + + [lastX, lastY] = [pos.x, pos.y]; + } + + function stopDrawing() { + isDrawing = false; + } + + function getPointerPos(e) { + const rect = canvas.getBoundingClientRect(); + let clientX, clientY; + + if (e.type.includes('touch')) { + clientX = e.touches[0].clientX; + clientY = e.touches[0].clientY; + } else { + clientX = e.clientX; + clientY = e.clientY; + } + + return { + x: (clientX - rect.left) * (canvas.width / dpr / rect.width), + y: (clientY - rect.top) * (canvas.height / dpr / rect.height) + }; + } + + // Event handlers for controls + colorPicker.addEventListener('input', (e) => { + ctx.strokeStyle = e.target.value; + }); + + brushSize.addEventListener('input', (e) => { + const size = e.target.value; + ctx.lineWidth = size; + brushSizeValue.textContent = size; + }); + + clearButton.addEventListener('click', () => { + if (confirm('Are you sure you want to clear the canvas?')) { + const width = canvas.width / dpr; + const height = canvas.height / dpr; + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, width, height); + } + }); + + saveButton.addEventListener('click', () => { + // Create a temporary canvas for saving at original resolution + const tempCanvas = document.createElement('canvas'); + const tempCtx = tempCanvas.getContext('2d'); + + tempCanvas.width = canvas.width; + tempCanvas.height = canvas.height; + + // Draw the high-resolution content to temp canvas + tempCtx.drawImage(canvas, 0, 0); + + // Create download link + const link = document.createElement('a'); + link.download = `drawing-${new Date().getTime()}.png`; + link.href = tempCanvas.toDataURL('image/png'); + link.click(); + }); + + // Pointer event handlers for drawing + canvas.addEventListener('pointerdown', startDrawing); + canvas.addEventListener('pointermove', draw); + canvas.addEventListener('pointerup', stopDrawing); + canvas.addEventListener('pointerout', stopDrawing); + + // Touch event handlers for mobile devices + canvas.addEventListener('touchstart', (e) => { + e.preventDefault(); + startDrawing(e); + }); + + canvas.addEventListener('touchmove', (e) => { + e.preventDefault(); + draw(e); + }); + + canvas.addEventListener('touchend', stopDrawing); + + // Handle window resize + let resizeTimeout; + window.addEventListener('resize', () => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + if (confirm('Resizing will clear your current drawing. Continue?')) { + initCanvas(); + } + }, 250); + }); + + // Prevent context menu on canvas + canvas.addEventListener('contextmenu', (e) => e.preventDefault()); + + // Initialize the canvas + initCanvas(); +}); \ No newline at end of file diff --git a/projects/drawing-canvas/styles.css b/projects/drawing-canvas/styles.css index 9ed19e3..b09454d 100644 --- a/projects/drawing-canvas/styles.css +++ b/projects/drawing-canvas/styles.css @@ -1,8 +1,11 @@ -/* TODO: Style the drawing canvas and toolbar */ + /* TODO: Style the drawing canvas and toolbar */ :root { --bg: #0b1220; --fg: #f8fafc; --accent: #f472b6; + --toolbar-bg: #1e293b; + --toolbar-border: #334155; + --canvas-bg: #ffffff; } * { box-sizing: border-box; } body { @@ -13,5 +16,128 @@ body { min-height: 100vh; display: grid; place-items: center; + padding: 1rem; } -#app { width: min(94vw, 960px); } +#app { + width: min(94vw, 960px); + display: flex; + flex-direction: column; + gap: 1rem; +} + +header h1 { + text-align: center; + margin: 0 0 1rem 0; + color: var(--accent); +} + +.canvas-container { + position: relative; + width: 100%; + height: 60vh; + border: 2px solid var(--toolbar-border); + border-radius: 8px; + overflow: hidden; + background: var(--canvas-bg); +} + +#drawingCanvas { + display: block; + width: 100%; + height: 100%; + cursor: crosshair; + touch-action: none; +} + +.toolbar { + display: flex; + flex-wrap: wrap; + gap: 1rem; + padding: 1rem; + background: var(--toolbar-bg); + border: 1px solid var(--toolbar-border); + border-radius: 8px; + align-items: center; + justify-content: center; +} + +.tool-group { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.tool-group label { + font-size: 0.9rem; + font-weight: 500; +} + +#colorPicker { + width: 50px; + height: 40px; + border: 2px solid var(--toolbar-border); + border-radius: 4px; + cursor: pointer; + background: #000000; +} + +#brushSize { + width: 120px; +} + +#brushSizeValue { + min-width: 30px; + text-align: center; + font-size: 0.9rem; +} + +.button { + padding: 0.75rem 1.5rem; + background: var(--accent); + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; + font-weight: 500; + transition: opacity 0.2s; +} + +.button:hover { + opacity: 0.9; +} + +.button:active { + transform: translateY(1px); +} + +.button.clear { + background: #ef4444; +} + +.button.save { + background: #10b981; +} + +.warning { + color: #f59e0b; + font-size: 0.8rem; + text-align: center; + margin-top: 0.5rem; +} + +/* Responsive design */ +@media (max-width: 768px) { + .toolbar { + flex-direction: column; + align-items: stretch; + } + + .tool-group { + justify-content: space-between; + } + + .canvas-container { + height: 50vh; + } +} \ No newline at end of file