// modules/canvas-animations/CanvasManager.js import { useEvent } from '../../core/useEvent.js'; import { Logger } from '../../core/logger.js'; /** * Canvas Manager - Handles canvas setup, resizing, and basic operations */ export class CanvasManager { constructor(canvas, options = {}) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.options = { responsive: true, pixelRatio: window.devicePixelRatio || 1, ...options }; this.animationId = null; this.isAnimating = false; this.init(); } /** * Initialize canvas manager */ init() { this.setupCanvas(); if (this.options.responsive) { this.setupResponsive(); } Logger.info('[CanvasManager] Initialized', { width: this.canvas.width, height: this.canvas.height, pixelRatio: this.options.pixelRatio }); } /** * Setup initial canvas properties */ setupCanvas() { this.resize(); // Set CSS to prevent blurriness on high-DPI displays this.canvas.style.width = this.canvas.width + 'px'; this.canvas.style.height = this.canvas.height + 'px'; // Scale context for high-DPI displays if (this.options.pixelRatio > 1) { this.ctx.scale(this.options.pixelRatio, this.options.pixelRatio); } } /** * Setup responsive behavior */ setupResponsive() { // Resize on window resize useEvent(window, 'resize', () => { this.resize(); }, 'canvas-manager'); // Optional: Resize on orientation change for mobile useEvent(window, 'orientationchange', () => { setTimeout(() => this.resize(), 100); }, 'canvas-manager'); } /** * Resize canvas to match container or window */ resize() { const container = this.canvas.parentElement; const pixelRatio = this.options.pixelRatio; // Get display size let displayWidth, displayHeight; if (container && getComputedStyle(container).position !== 'static') { // Use container size if it has positioning displayWidth = container.clientWidth; displayHeight = container.clientHeight; } else { // Fallback to canvas CSS size or window size displayWidth = this.canvas.clientWidth || window.innerWidth; displayHeight = this.canvas.clientHeight || window.innerHeight; } // Set actual canvas size this.canvas.width = Math.floor(displayWidth * pixelRatio); this.canvas.height = Math.floor(displayHeight * pixelRatio); // Set CSS size this.canvas.style.width = displayWidth + 'px'; this.canvas.style.height = displayHeight + 'px'; // Re-scale context if needed if (pixelRatio > 1) { this.ctx.scale(pixelRatio, pixelRatio); } Logger.info('[CanvasManager] Resized', { displayWidth, displayHeight, canvasWidth: this.canvas.width, canvasHeight: this.canvas.height }); } /** * Clear the entire canvas */ clear() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } /** * Get canvas dimensions (display size, not pixel size) */ getSize() { return { width: this.canvas.clientWidth, height: this.canvas.clientHeight, pixelWidth: this.canvas.width, pixelHeight: this.canvas.height }; } /** * Get mouse position relative to canvas */ getMousePosition(event) { const rect = this.canvas.getBoundingClientRect(); return { x: (event.clientX - rect.left) * this.options.pixelRatio, y: (event.clientY - rect.top) * this.options.pixelRatio }; } /** * Start animation loop */ startAnimation(animationFunction) { if (this.isAnimating) { this.stopAnimation(); } this.isAnimating = true; const animate = (timestamp) => { if (!this.isAnimating) return; animationFunction(timestamp); this.animationId = requestAnimationFrame(animate); }; this.animationId = requestAnimationFrame(animate); Logger.info('[CanvasManager] Animation started'); } /** * Stop animation loop */ stopAnimation() { if (this.animationId) { cancelAnimationFrame(this.animationId); this.animationId = null; } this.isAnimating = false; Logger.info('[CanvasManager] Animation stopped'); } /** * Cleanup canvas manager */ destroy() { this.stopAnimation(); // Event cleanup is handled by useEvent system Logger.info('[CanvasManager] Destroyed'); } }