Files
michaelschiemer/resources/js/modules/canvas-animations/ScrollEffects.js
Michael Schiemer 55a330b223 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
2025-08-11 20:13:26 +02:00

244 lines
8.0 KiB
JavaScript

// modules/canvas-animations/ScrollEffects.js
import { CanvasManager } from './CanvasManager.js';
import { useEvent } from '../../core/useEvent.js';
import { Logger } from '../../core/logger.js';
/**
* Scroll-based Canvas Effects - Parallax and scroll animations
*/
export const ScrollEffects = {
/**
* Initialize parallax canvas effect
*/
initParallax(canvas, config) {
const manager = new CanvasManager(canvas);
const elements = this.createParallaxElements(canvas, config);
let ticking = false;
const updateParallax = () => {
if (!ticking) {
requestAnimationFrame(() => {
this.renderParallax(manager, elements, config);
ticking = false;
});
ticking = true;
}
};
// Listen to scroll events
useEvent(window, 'scroll', updateParallax, 'scroll-parallax');
useEvent(window, 'resize', updateParallax, 'scroll-parallax');
// Initial render
updateParallax();
Logger.info('[ScrollEffects] Parallax initialized with', elements.length, 'elements');
},
/**
* Create parallax elements based on config
*/
createParallaxElements(canvas, config) {
const elements = [];
const layerCount = config.layers || 3;
const elementCount = config.elements || 20;
for (let i = 0; i < elementCount; i++) {
elements.push({
x: Math.random() * canvas.clientWidth,
y: Math.random() * canvas.clientHeight * 2, // Allow elements outside viewport
size: Math.random() * 20 + 5,
layer: Math.floor(Math.random() * layerCount),
speed: 0.1 + (Math.random() * 0.5), // Different parallax speeds
opacity: Math.random() * 0.7 + 0.3,
color: this.getLayerColor(Math.floor(Math.random() * layerCount), config)
});
}
return elements;
},
/**
* Get color for parallax layer
*/
getLayerColor(layer, config) {
const colors = config.colors || [
'rgba(100, 150, 255, 0.6)', // Front layer - more opaque
'rgba(150, 100, 255, 0.4)', // Middle layer
'rgba(200, 100, 150, 0.2)' // Back layer - more transparent
];
return colors[layer] || colors[0];
},
/**
* Render parallax effect
*/
renderParallax(manager, elements, config) {
manager.clear();
const scrollY = window.pageYOffset;
const canvasRect = manager.canvas.getBoundingClientRect();
const canvasTop = canvasRect.top + scrollY;
// Calculate relative scroll position
const relativeScroll = scrollY - canvasTop;
const scrollProgress = relativeScroll / window.innerHeight;
elements.forEach(element => {
// Apply parallax offset based on layer and scroll
const parallaxOffset = scrollProgress * element.speed * 100;
const y = element.y - parallaxOffset;
// Only render elements that are potentially visible
if (y > -element.size && y < manager.canvas.clientHeight + element.size) {
manager.ctx.save();
manager.ctx.globalAlpha = element.opacity;
manager.ctx.fillStyle = element.color;
manager.ctx.beginPath();
manager.ctx.arc(element.x, y, element.size, 0, Math.PI * 2);
manager.ctx.fill();
manager.ctx.restore();
}
});
},
/**
* Initialize scroll-based animations
*/
initScrollAnimation(canvas, config) {
const manager = new CanvasManager(canvas);
const animationType = config.animation || 'wave';
let ticking = false;
const updateAnimation = () => {
if (!ticking) {
requestAnimationFrame(() => {
this.renderScrollAnimation(manager, animationType, config);
ticking = false;
});
ticking = true;
}
};
useEvent(window, 'scroll', updateAnimation, 'scroll-animation');
useEvent(window, 'resize', updateAnimation, 'scroll-animation');
// Initial render
updateAnimation();
Logger.info('[ScrollEffects] Scroll animation initialized:', animationType);
},
/**
* Render scroll-based animations
*/
renderScrollAnimation(manager, animationType, config) {
manager.clear();
const scrollY = window.pageYOffset;
const canvasRect = manager.canvas.getBoundingClientRect();
const canvasTop = canvasRect.top + scrollY;
const relativeScroll = scrollY - canvasTop;
const scrollProgress = Math.max(0, Math.min(1, relativeScroll / window.innerHeight));
switch (animationType) {
case 'wave':
this.renderWaveAnimation(manager, scrollProgress, config);
break;
case 'progress':
this.renderProgressAnimation(manager, scrollProgress, config);
break;
case 'morph':
this.renderMorphAnimation(manager, scrollProgress, config);
break;
default:
this.renderWaveAnimation(manager, scrollProgress, config);
}
},
/**
* Render wave animation based on scroll
*/
renderWaveAnimation(manager, progress, config) {
const { ctx } = manager;
const { width, height } = manager.getSize();
ctx.strokeStyle = config.color || 'rgba(100, 150, 255, 0.8)';
ctx.lineWidth = config.lineWidth || 3;
ctx.beginPath();
const amplitude = (config.amplitude || 50) * progress;
const frequency = config.frequency || 0.02;
const phase = progress * Math.PI * 2;
for (let x = 0; x <= width; x += 2) {
const y = height / 2 + Math.sin(x * frequency + phase) * amplitude;
if (x === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
},
/**
* Render progress bar animation
*/
renderProgressAnimation(manager, progress, config) {
const { ctx } = manager;
const { width, height } = manager.getSize();
const barHeight = config.barHeight || 10;
const y = height / 2 - barHeight / 2;
// Background
ctx.fillStyle = config.backgroundColor || 'rgba(255, 255, 255, 0.2)';
ctx.fillRect(0, y, width, barHeight);
// Progress
ctx.fillStyle = config.color || 'rgba(100, 150, 255, 0.8)';
ctx.fillRect(0, y, width * progress, barHeight);
},
/**
* Render morphing shapes
*/
renderMorphAnimation(manager, progress, config) {
const { ctx } = manager;
const { width, height } = manager.getSize();
ctx.fillStyle = config.color || 'rgba(100, 150, 255, 0.6)';
const centerX = width / 2;
const centerY = height / 2;
const maxRadius = Math.min(width, height) / 3;
ctx.beginPath();
const points = config.points || 6;
for (let i = 0; i <= points; i++) {
const angle = (i / points) * Math.PI * 2;
const radiusVariation = Math.sin(progress * Math.PI * 4 + angle * 3) * 0.3 + 1;
const radius = maxRadius * progress * radiusVariation;
const x = centerX + Math.cos(angle) * radius;
const y = centerY + Math.sin(angle) * radius;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.fill();
}
};