Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
303 lines
8.9 KiB
JavaScript
303 lines
8.9 KiB
JavaScript
/**
|
|
* Unified Animation System
|
|
*
|
|
* Consolidates all scroll animation modules into a single, unified system.
|
|
* Replaces: scrollfx, parallax, scroll-timeline, scroll-loop, scroll-dependent, sticky-fade, sticky-steps
|
|
*/
|
|
|
|
import { Logger } from '../../core/logger.js';
|
|
import { ScrollAnimation } from './ScrollAnimation.js';
|
|
import { TimelineAnimation } from './TimelineAnimation.js';
|
|
|
|
/**
|
|
* AnimationSystem - Unified animation system
|
|
*/
|
|
export class AnimationSystem {
|
|
constructor(config = {}) {
|
|
this.config = {
|
|
enabled: config.enabled ?? true,
|
|
useIntersectionObserver: config.useIntersectionObserver ?? true,
|
|
throttleDelay: config.throttleDelay || 16, // ~60fps
|
|
...config
|
|
};
|
|
|
|
this.animations = new Map(); // Map<element, Animation>
|
|
this.observers = new Map(); // Map<element, IntersectionObserver>
|
|
this.scrollHandler = null;
|
|
this.isScrolling = false;
|
|
|
|
// Initialize
|
|
if (this.config.enabled) {
|
|
this.init();
|
|
}
|
|
|
|
Logger.info('[AnimationSystem] Initialized', {
|
|
enabled: this.config.enabled,
|
|
useIntersectionObserver: this.config.useIntersectionObserver
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a new AnimationSystem instance
|
|
*/
|
|
static create(config = {}) {
|
|
return new AnimationSystem(config);
|
|
}
|
|
|
|
/**
|
|
* Initialize animation system
|
|
*/
|
|
init() {
|
|
// Set up scroll handler
|
|
if (!this.config.useIntersectionObserver) {
|
|
this.setupScrollHandler();
|
|
}
|
|
|
|
// Auto-initialize elements with data attributes
|
|
this.autoInitialize();
|
|
}
|
|
|
|
/**
|
|
* Set up scroll handler
|
|
*/
|
|
setupScrollHandler() {
|
|
let ticking = false;
|
|
|
|
this.scrollHandler = () => {
|
|
if (!ticking) {
|
|
window.requestAnimationFrame(() => {
|
|
this.updateAnimations();
|
|
ticking = false;
|
|
});
|
|
ticking = true;
|
|
}
|
|
};
|
|
|
|
window.addEventListener('scroll', this.scrollHandler, { passive: true });
|
|
}
|
|
|
|
/**
|
|
* Auto-initialize elements with data attributes
|
|
*/
|
|
autoInitialize() {
|
|
// Fade in on scroll (scrollfx)
|
|
this.initializeFadeIn();
|
|
|
|
// Parallax
|
|
this.initializeParallax();
|
|
|
|
// Scroll timeline
|
|
this.initializeTimeline();
|
|
|
|
// Sticky fade
|
|
this.initializeStickyFade();
|
|
|
|
// Sticky steps
|
|
this.initializeStickySteps();
|
|
}
|
|
|
|
/**
|
|
* Initialize fade-in animations (scrollfx)
|
|
*/
|
|
initializeFadeIn() {
|
|
const elements = document.querySelectorAll('.fade-in-on-scroll, .zoom-in, [data-animate="fade-in"]');
|
|
elements.forEach(element => {
|
|
this.registerAnimation(element, {
|
|
type: 'fade-in',
|
|
offset: parseFloat(element.dataset.offset) || 0.85,
|
|
delay: parseFloat(element.dataset.delay) || 0,
|
|
once: element.dataset.once !== 'false'
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize parallax animations
|
|
*/
|
|
initializeParallax() {
|
|
const elements = document.querySelectorAll('[data-parallax], .parallax');
|
|
elements.forEach(element => {
|
|
const speed = parseFloat(element.dataset.parallax || element.dataset.speed) || 0.5;
|
|
this.registerAnimation(element, {
|
|
type: 'parallax',
|
|
speed
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize timeline animations (scroll-timeline)
|
|
*/
|
|
initializeTimeline() {
|
|
const elements = document.querySelectorAll('[data-scroll-timeline], [data-scroll-step]');
|
|
elements.forEach(element => {
|
|
this.registerAnimation(element, {
|
|
type: 'timeline',
|
|
steps: element.dataset.scrollSteps ? parseInt(element.dataset.scrollSteps) : null,
|
|
triggerPoint: parseFloat(element.dataset.triggerPoint) || 0.4
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize sticky fade animations
|
|
*/
|
|
initializeStickyFade() {
|
|
const elements = document.querySelectorAll('[data-sticky-fade], .sticky-fade');
|
|
elements.forEach(element => {
|
|
this.registerAnimation(element, {
|
|
type: 'sticky-fade',
|
|
fadeStart: parseFloat(element.dataset.fadeStart) || 0,
|
|
fadeEnd: parseFloat(element.dataset.fadeEnd) || 1
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize sticky steps animations
|
|
*/
|
|
initializeStickySteps() {
|
|
const elements = document.querySelectorAll('[data-sticky-steps], .sticky-steps');
|
|
elements.forEach(element => {
|
|
const steps = element.dataset.stickySteps ? parseInt(element.dataset.stickySteps) : 3;
|
|
this.registerAnimation(element, {
|
|
type: 'sticky-steps',
|
|
steps
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Register an animation
|
|
*/
|
|
registerAnimation(element, config) {
|
|
if (this.animations.has(element)) {
|
|
Logger.warn('[AnimationSystem] Animation already registered for element', element);
|
|
return;
|
|
}
|
|
|
|
let animation;
|
|
|
|
switch (config.type) {
|
|
case 'fade-in':
|
|
case 'zoom-in':
|
|
animation = new ScrollAnimation(element, {
|
|
type: config.type,
|
|
offset: config.offset || 0.85,
|
|
delay: config.delay || 0,
|
|
once: config.once !== false
|
|
});
|
|
break;
|
|
|
|
case 'parallax':
|
|
animation = new ScrollAnimation(element, {
|
|
type: 'parallax',
|
|
speed: config.speed || 0.5
|
|
});
|
|
break;
|
|
|
|
case 'timeline':
|
|
animation = new TimelineAnimation(element, {
|
|
steps: config.steps,
|
|
triggerPoint: config.triggerPoint || 0.4
|
|
});
|
|
break;
|
|
|
|
case 'sticky-fade':
|
|
animation = new ScrollAnimation(element, {
|
|
type: 'sticky-fade',
|
|
fadeStart: config.fadeStart || 0,
|
|
fadeEnd: config.fadeEnd || 1
|
|
});
|
|
break;
|
|
|
|
case 'sticky-steps':
|
|
animation = new ScrollAnimation(element, {
|
|
type: 'sticky-steps',
|
|
steps: config.steps || 3
|
|
});
|
|
break;
|
|
|
|
default:
|
|
Logger.warn('[AnimationSystem] Unknown animation type', config.type);
|
|
return;
|
|
}
|
|
|
|
this.animations.set(element, animation);
|
|
|
|
// Set up observer if using IntersectionObserver
|
|
if (this.config.useIntersectionObserver) {
|
|
this.setupObserver(element, animation);
|
|
}
|
|
|
|
Logger.debug('[AnimationSystem] Animation registered', { element, type: config.type });
|
|
}
|
|
|
|
/**
|
|
* Set up IntersectionObserver for element
|
|
*/
|
|
setupObserver(element, animation) {
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
animation.enter();
|
|
} else if (!animation.config.once) {
|
|
animation.exit();
|
|
}
|
|
});
|
|
}, {
|
|
threshold: animation.config.offset || 0.85,
|
|
rootMargin: '0px'
|
|
});
|
|
|
|
observer.observe(element);
|
|
this.observers.set(element, observer);
|
|
}
|
|
|
|
/**
|
|
* Update all animations (for scroll-based updates)
|
|
*/
|
|
updateAnimations() {
|
|
this.animations.forEach((animation, element) => {
|
|
if (animation.needsUpdate) {
|
|
animation.update();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Remove animation
|
|
*/
|
|
removeAnimation(element) {
|
|
const animation = this.animations.get(element);
|
|
if (animation) {
|
|
animation.destroy();
|
|
this.animations.delete(element);
|
|
}
|
|
|
|
const observer = this.observers.get(element);
|
|
if (observer) {
|
|
observer.disconnect();
|
|
this.observers.delete(element);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroy animation system
|
|
*/
|
|
destroy() {
|
|
// Remove all animations
|
|
this.animations.forEach((animation, element) => {
|
|
this.removeAnimation(element);
|
|
});
|
|
|
|
// Remove scroll handler
|
|
if (this.scrollHandler) {
|
|
window.removeEventListener('scroll', this.scrollHandler);
|
|
}
|
|
|
|
Logger.info('[AnimationSystem] Destroyed');
|
|
}
|
|
}
|
|
|