class HippieCrosshair { constructor(canvas, options = {}) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.debug = options.debug || false; const defaults = { crosshair: { size: 16, thickness: 2, color: '#000', gapSize: 8, style: 'cross' }, connector: { distance: 128, // Distance to draw next symbol spacing: 64, // Space between symbols size: 8, color: '#000', style: 'arrow', visibility: true }, line: { color: 'rgba(0, 0, 0, 0.1)', width: 1 } }; const merged = this.mergeOptions(defaults, options); const { crosshair, connector, line } = merged; // Crosshair options this.size = crosshair.size; this.thickness = crosshair.thickness; this.color = crosshair.color; this.gapSize = crosshair.gapSize; this.style = crosshair.style; // Connector options this.distance = connector.distance; this.spacing = connector.spacing; this.connectorSize = connector.size; this.connectorColor = connector.color; this.connectorStyle = connector.style; this.connectorShow = connector.visibility; // Line options this.lineColor = line.color || '#fff'; this.lineWidth = line.width || 1; this.mouseX = canvas.width / 2; this.mouseY = canvas.height / 2; // Animation control this.isAnimating = true; this.animationFrameId = null; this.setupEventListeners(); this.animate(); } mergeOptions(defaults, options) { const merged = JSON.parse(JSON.stringify(defaults)); if (options.crosshair) { Object.assign(merged.crosshair, options.crosshair); } if (options.connector) { Object.assign(merged.connector, options.connector); } if (options.line) { Object.assign(merged.line, options.line); } return merged; } setupEventListeners() { document.addEventListener('mousemove', (event) => { this.mouseX = event.clientX; this.mouseY = event.clientY; }); } animate() { if (!this.isAnimating) { this.animationFrameId = requestAnimationFrame(() => this.animate()); return; } this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); if (this.connectorShow) { if (this.debug) this.drawLine(); this.drawConnector(); } // TODO: Autom. Zug zum Zentrum hin ermöglichen this.drawCrosshair(); this.animationFrameId = requestAnimationFrame(() => this.animate()); } drawLine() { const centerX = this.canvas.width / 2; const centerY = this.canvas.height / 2; this.ctx.strokeStyle = this.lineColor; this.ctx.lineWidth = this.lineWidth; this.ctx.beginPath(); this.ctx.moveTo(centerX, centerY); this.ctx.lineTo(this.mouseX, this.mouseY); this.ctx.stroke(); } // TODO: Ausblenden nach Distanz drawConnector() { const centerX = this.canvas.width / 2; const centerY = this.canvas.height / 2; const dx = this.mouseX - centerX; const dy = this.mouseY - centerY; const delta = Math.sqrt(dx * dx + dy * dy); // Only draw connectors if cursor is far enough from center if (delta < this.distance) { return; } const angle = Math.atan2(dy, dx); const count = Math.floor((delta - this.distance) / this.spacing); for (let i = 0; i < count; i++) { const distance = this.distance + (i * this.spacing); const x = centerX + Math.cos(angle) * distance; const y = centerY + Math.sin(angle) * distance; this.drawSymbol(x, y, angle); } } drawSymbol(x, y, angle = 0) { this.ctx.fillStyle = this.connectorColor; this.ctx.strokeStyle = this.connectorColor; this.ctx.lineWidth = 1; switch (this.connectorStyle) { case 'circle': this.ctx.beginPath(); this.ctx.arc(x, y, this.connectorSize / 2, 0, Math.PI * 2); this.ctx.fill(); break; case 'diamond': const size = this.connectorSize - (this.connectorSize / 4); this.ctx.beginPath(); this.ctx.moveTo(x, y - size); this.ctx.lineTo(x + size, y); this.ctx.lineTo(x, y + size); this.ctx.lineTo(x - size, y); this.ctx.closePath(); this.ctx.fill(); break; case 'square': this.ctx.fillRect( x - this.connectorSize / 2, y - this.connectorSize / 2, this.connectorSize, this.connectorSize ); break; case 'arrow': this.arrow(x, y, angle); break; } } arrow(x, y, angle) { const size = this.connectorSize - (this.connectorSize / 4); this.ctx.save(); this.ctx.translate(x, y); this.ctx.rotate(angle); // Arrow pointing right this.ctx.beginPath(); this.ctx.moveTo(size, 0); // Tip this.ctx.lineTo(-size, -size); // Back left // this.ctx.lineTo(-size * 0.4, 0); // Middle this.ctx.lineTo(-size, size); // Back right this.ctx.closePath(); this.ctx.fill(); this.ctx.restore(); } drawCrosshair() { this.ctx.strokeStyle = this.color; this.ctx.lineWidth = this.thickness; this.ctx.lineCap = 'round'; switch (this.style) { case 'cross': this.cross(); break; case 'circle': this.circle(); break; case 'dot': this.dot(); break; case 'level': this.level(); break; } } cross() { // Horizontal line this.ctx.beginPath(); this.ctx.moveTo(this.mouseX - this.size, this.mouseY); this.ctx.lineTo(this.mouseX - this.gapSize, this.mouseY); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.moveTo(this.mouseX + this.gapSize, this.mouseY); this.ctx.lineTo(this.mouseX + this.size, this.mouseY); this.ctx.stroke(); // Vertical line this.ctx.beginPath(); this.ctx.moveTo(this.mouseX, this.mouseY - this.size); this.ctx.lineTo(this.mouseX, this.mouseY - this.gapSize); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.moveTo(this.mouseX, this.mouseY + this.gapSize); this.ctx.lineTo(this.mouseX, this.mouseY + this.size); this.ctx.stroke(); } circle() { // Outer circle this.ctx.beginPath(); this.ctx.arc(this.mouseX, this.mouseY, this.size, 0, Math.PI * 2); this.ctx.stroke(); // Inner dot this.ctx.fillStyle = this.color; this.ctx.beginPath(); this.ctx.arc(this.mouseX, this.mouseY, this.thickness, 0, Math.PI * 2); this.ctx.fill(); } dot() { this.ctx.fillStyle = this.color; this.ctx.beginPath(); this.ctx.arc(this.mouseX, this.mouseY, this.size / 4, 0, Math.PI * 2); this.ctx.fill(); } level() { this.ctx.beginPath(); this.ctx.moveTo(this.mouseX - this.size * 2, this.mouseY); this.ctx.lineTo(this.mouseX - this.gapSize * 2, this.mouseY); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.arc(this.mouseX, this.mouseY, this.size, 0, Math.PI); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.moveTo(this.mouseX + this.gapSize * 2, this.mouseY); this.ctx.lineTo(this.mouseX + this.size * 2, this.mouseY); this.ctx.stroke(); this.ctx.fillStyle = this.color; this.ctx.beginPath(); this.ctx.arc(this.mouseX, this.mouseY, this.thickness, 0, Math.PI * 2); this.ctx.fill(); } setCrosshairStyle(style) { this.style = style; } setCrosshairColor(color) { this.color = color; } setConnectorStyle(style) { this.connectorStyle = style; } setConnectorColor(color) { this.connectorColor = color; } setConnectorVisibility(visible) { this.connectorShow = visible; } startAnimation() { this.isAnimating = true; } stopAnimation() { this.isAnimating = false; } toggleAnimation() { this.isAnimating = !this.isAnimating; } }