Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
335
resources/js/modules/canvas-animations/InteractiveEffects.js
Normal file
335
resources/js/modules/canvas-animations/InteractiveEffects.js
Normal file
@@ -0,0 +1,335 @@
|
||||
// modules/canvas-animations/InteractiveEffects.js
|
||||
import { CanvasManager } from './CanvasManager.js';
|
||||
import { useEvent } from '../../core/useEvent.js';
|
||||
import { Logger } from '../../core/logger.js';
|
||||
|
||||
/**
|
||||
* Interactive Canvas Effects - Mouse/touch interactions, hover effects
|
||||
*/
|
||||
export const InteractiveEffects = {
|
||||
|
||||
/**
|
||||
* Initialize interactive canvas
|
||||
*/
|
||||
init(canvas, config) {
|
||||
const manager = new CanvasManager(canvas);
|
||||
const effectType = config.effect || 'ripple';
|
||||
|
||||
const state = {
|
||||
mouse: { x: 0, y: 0, isOver: false },
|
||||
effects: [],
|
||||
lastTime: 0
|
||||
};
|
||||
|
||||
this.setupInteractionEvents(canvas, manager, state, config);
|
||||
this.startAnimationLoop(manager, state, effectType, config);
|
||||
|
||||
Logger.info('[InteractiveEffects] Initialized with effect:', effectType);
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup mouse/touch interaction events
|
||||
*/
|
||||
setupInteractionEvents(canvas, manager, state, config) {
|
||||
// Mouse events
|
||||
useEvent(canvas, 'mousemove', (e) => {
|
||||
const pos = manager.getMousePosition(e);
|
||||
state.mouse.x = pos.x;
|
||||
state.mouse.y = pos.y;
|
||||
|
||||
if (config.effect === 'trail') {
|
||||
this.addTrailPoint(state, pos.x, pos.y);
|
||||
}
|
||||
}, 'interactive-effects');
|
||||
|
||||
useEvent(canvas, 'mouseenter', () => {
|
||||
state.mouse.isOver = true;
|
||||
}, 'interactive-effects');
|
||||
|
||||
useEvent(canvas, 'mouseleave', () => {
|
||||
state.mouse.isOver = false;
|
||||
}, 'interactive-effects');
|
||||
|
||||
useEvent(canvas, 'click', (e) => {
|
||||
const pos = manager.getMousePosition(e);
|
||||
this.addClickEffect(state, pos.x, pos.y, config);
|
||||
}, 'interactive-effects');
|
||||
|
||||
// Touch events for mobile
|
||||
useEvent(canvas, 'touchstart', (e) => {
|
||||
e.preventDefault();
|
||||
const touch = e.touches[0];
|
||||
const pos = manager.getMousePosition(touch);
|
||||
this.addClickEffect(state, pos.x, pos.y, config);
|
||||
}, 'interactive-effects');
|
||||
|
||||
useEvent(canvas, 'touchmove', (e) => {
|
||||
e.preventDefault();
|
||||
const touch = e.touches[0];
|
||||
const pos = manager.getMousePosition(touch);
|
||||
state.mouse.x = pos.x;
|
||||
state.mouse.y = pos.y;
|
||||
state.mouse.isOver = true;
|
||||
|
||||
if (config.effect === 'trail') {
|
||||
this.addTrailPoint(state, pos.x, pos.y);
|
||||
}
|
||||
}, 'interactive-effects');
|
||||
|
||||
useEvent(canvas, 'touchend', () => {
|
||||
state.mouse.isOver = false;
|
||||
}, 'interactive-effects');
|
||||
},
|
||||
|
||||
/**
|
||||
* Start animation loop
|
||||
*/
|
||||
startAnimationLoop(manager, state, effectType, config) {
|
||||
const animate = (timestamp) => {
|
||||
const deltaTime = timestamp - state.lastTime;
|
||||
state.lastTime = timestamp;
|
||||
|
||||
manager.clear();
|
||||
|
||||
switch (effectType) {
|
||||
case 'ripple':
|
||||
this.renderRippleEffect(manager, state, config);
|
||||
break;
|
||||
case 'trail':
|
||||
this.renderTrailEffect(manager, state, config);
|
||||
break;
|
||||
case 'particles':
|
||||
this.renderParticleEffect(manager, state, config, deltaTime);
|
||||
break;
|
||||
case 'magnetic':
|
||||
this.renderMagneticEffect(manager, state, config);
|
||||
break;
|
||||
default:
|
||||
this.renderRippleEffect(manager, state, config);
|
||||
}
|
||||
|
||||
// Update effects
|
||||
this.updateEffects(state.effects, deltaTime);
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add click effect (ripple, explosion, etc.)
|
||||
*/
|
||||
addClickEffect(state, x, y, config) {
|
||||
const effect = {
|
||||
x,
|
||||
y,
|
||||
age: 0,
|
||||
maxAge: config.duration || 1000,
|
||||
type: 'click',
|
||||
intensity: config.intensity || 1
|
||||
};
|
||||
|
||||
state.effects.push(effect);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add trail point for mouse trail effect
|
||||
*/
|
||||
addTrailPoint(state, x, y) {
|
||||
const point = {
|
||||
x,
|
||||
y,
|
||||
age: 0,
|
||||
maxAge: 500,
|
||||
type: 'trail'
|
||||
};
|
||||
|
||||
state.effects.push(point);
|
||||
|
||||
// Limit trail length
|
||||
const trailLength = 20;
|
||||
const trailPoints = state.effects.filter(e => e.type === 'trail');
|
||||
if (trailPoints.length > trailLength) {
|
||||
const oldestIndex = state.effects.indexOf(trailPoints[0]);
|
||||
state.effects.splice(oldestIndex, 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render ripple effect
|
||||
*/
|
||||
renderRippleEffect(manager, state, config) {
|
||||
const { ctx } = manager;
|
||||
|
||||
// Draw active ripples
|
||||
state.effects.forEach(effect => {
|
||||
if (effect.type === 'click') {
|
||||
const progress = effect.age / effect.maxAge;
|
||||
const radius = progress * (config.maxRadius || 100);
|
||||
const opacity = (1 - progress) * 0.8;
|
||||
|
||||
ctx.save();
|
||||
ctx.globalAlpha = opacity;
|
||||
ctx.strokeStyle = config.color || 'rgba(100, 150, 255, 1)';
|
||||
ctx.lineWidth = config.lineWidth || 3;
|
||||
ctx.beginPath();
|
||||
ctx.arc(effect.x / manager.options.pixelRatio, effect.y / manager.options.pixelRatio, radius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
});
|
||||
|
||||
// Draw hover effect
|
||||
if (state.mouse.isOver) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.3;
|
||||
ctx.fillStyle = config.hoverColor || 'rgba(100, 150, 255, 0.3)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
state.mouse.x / manager.options.pixelRatio,
|
||||
state.mouse.y / manager.options.pixelRatio,
|
||||
config.hoverRadius || 30,
|
||||
0,
|
||||
Math.PI * 2
|
||||
);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render trail effect
|
||||
*/
|
||||
renderTrailEffect(manager, state, config) {
|
||||
const { ctx } = manager;
|
||||
const trailPoints = state.effects.filter(e => e.type === 'trail');
|
||||
|
||||
if (trailPoints.length < 2) return;
|
||||
|
||||
ctx.save();
|
||||
ctx.strokeStyle = config.color || 'rgba(100, 150, 255, 0.8)';
|
||||
ctx.lineWidth = config.lineWidth || 5;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
// Draw trail path
|
||||
ctx.beginPath();
|
||||
trailPoints.forEach((point, index) => {
|
||||
const progress = 1 - (point.age / point.maxAge);
|
||||
const x = point.x / manager.options.pixelRatio;
|
||||
const y = point.y / manager.options.pixelRatio;
|
||||
|
||||
ctx.globalAlpha = progress * 0.8;
|
||||
|
||||
if (index === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
});
|
||||
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
/**
|
||||
* Render particle effect
|
||||
*/
|
||||
renderParticleEffect(manager, state, config, deltaTime) {
|
||||
const { ctx } = manager;
|
||||
|
||||
// Spawn particles on mouse move
|
||||
if (state.mouse.isOver && Math.random() < 0.1) {
|
||||
const particle = {
|
||||
x: state.mouse.x,
|
||||
y: state.mouse.y,
|
||||
vx: (Math.random() - 0.5) * 4,
|
||||
vy: (Math.random() - 0.5) * 4,
|
||||
age: 0,
|
||||
maxAge: 1000,
|
||||
size: Math.random() * 5 + 2,
|
||||
type: 'particle'
|
||||
};
|
||||
state.effects.push(particle);
|
||||
}
|
||||
|
||||
// Update and draw particles
|
||||
state.effects.forEach(effect => {
|
||||
if (effect.type === 'particle') {
|
||||
// Update position
|
||||
effect.x += effect.vx;
|
||||
effect.y += effect.vy;
|
||||
effect.vy += 0.1; // Gravity
|
||||
|
||||
const progress = effect.age / effect.maxAge;
|
||||
const opacity = (1 - progress) * 0.8;
|
||||
|
||||
ctx.save();
|
||||
ctx.globalAlpha = opacity;
|
||||
ctx.fillStyle = config.color || 'rgba(100, 150, 255, 1)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
effect.x / manager.options.pixelRatio,
|
||||
effect.y / manager.options.pixelRatio,
|
||||
effect.size * (1 - progress * 0.5),
|
||||
0,
|
||||
Math.PI * 2
|
||||
);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Render magnetic effect
|
||||
*/
|
||||
renderMagneticEffect(manager, state, config) {
|
||||
const { ctx } = manager;
|
||||
const { width, height } = manager.getSize();
|
||||
|
||||
if (!state.mouse.isOver) return;
|
||||
|
||||
// Draw magnetic field lines
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
const mouseX = state.mouse.x / manager.options.pixelRatio;
|
||||
const mouseY = state.mouse.y / manager.options.pixelRatio;
|
||||
|
||||
ctx.save();
|
||||
ctx.strokeStyle = config.color || 'rgba(100, 150, 255, 0.6)';
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
const lines = 8;
|
||||
for (let i = 0; i < lines; i++) {
|
||||
const angle = (i / lines) * Math.PI * 2;
|
||||
const startX = centerX + Math.cos(angle) * 50;
|
||||
const startY = centerY + Math.sin(angle) * 50;
|
||||
|
||||
// Curve towards mouse
|
||||
const controlX = (startX + mouseX) / 2 + Math.sin(angle) * 30;
|
||||
const controlY = (startY + mouseY) / 2 + Math.cos(angle) * 30;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startX, startY);
|
||||
ctx.quadraticCurveTo(controlX, controlY, mouseX, mouseY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update all effects (age and cleanup)
|
||||
*/
|
||||
updateEffects(effects, deltaTime) {
|
||||
for (let i = effects.length - 1; i >= 0; i--) {
|
||||
effects[i].age += deltaTime;
|
||||
|
||||
if (effects[i].age >= effects[i].maxAge) {
|
||||
effects.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user