/** * Scroll Animation * * Handles various scroll-based animations: fade-in, zoom-in, parallax, sticky-fade, sticky-steps */ import { Logger } from '../../core/logger.js'; /** * ScrollAnimation - Individual scroll animation */ export class ScrollAnimation { constructor(element, config = {}) { this.element = element; this.config = { type: config.type || 'fade-in', offset: config.offset || 0.85, delay: config.delay || 0, once: config.once !== false, speed: config.speed || 0.5, fadeStart: config.fadeStart || 0, fadeEnd: config.fadeEnd || 1, steps: config.steps || 3, ...config }; this.triggered = false; this.needsUpdate = true; // Initialize based on type this.init(); } /** * Initialize animation */ init() { switch (this.config.type) { case 'fade-in': case 'zoom-in': this.initFadeIn(); break; case 'parallax': this.initParallax(); break; case 'sticky-fade': this.initStickyFade(); break; case 'sticky-steps': this.initStickySteps(); break; } } /** * Initialize fade-in animation */ initFadeIn() { this.element.style.opacity = '0'; this.element.style.transition = `opacity 0.6s ease, transform 0.6s ease`; this.element.style.transitionDelay = `${this.config.delay}s`; if (this.config.type === 'zoom-in') { this.element.style.transform = 'scale(0.9)'; } } /** * Initialize parallax animation */ initParallax() { // Parallax doesn't need initial setup this.needsUpdate = true; } /** * Initialize sticky fade animation */ initStickyFade() { this.element.style.position = 'sticky'; this.element.style.top = '0'; } /** * Initialize sticky steps animation */ initStickySteps() { this.element.style.position = 'sticky'; this.element.style.top = '0'; } /** * Enter animation (element enters viewport) */ enter() { if (this.triggered && this.config.once) { return; } this.triggered = true; switch (this.config.type) { case 'fade-in': this.element.style.opacity = '1'; this.element.classList.add('visible', 'entered'); break; case 'zoom-in': this.element.style.opacity = '1'; this.element.style.transform = 'scale(1)'; this.element.classList.add('visible', 'entered'); break; } } /** * Exit animation (element exits viewport) */ exit() { if (this.config.once) { return; } this.triggered = false; switch (this.config.type) { case 'fade-in': this.element.style.opacity = '0'; this.element.classList.remove('visible', 'entered'); break; case 'zoom-in': this.element.style.opacity = '0'; this.element.style.transform = 'scale(0.9)'; this.element.classList.remove('visible', 'entered'); break; } } /** * Update animation (for scroll-based animations) */ update() { const rect = this.element.getBoundingClientRect(); const viewportHeight = window.innerHeight; const scrollY = window.scrollY; switch (this.config.type) { case 'parallax': this.updateParallax(rect, scrollY); break; case 'sticky-fade': this.updateStickyFade(rect, viewportHeight); break; case 'sticky-steps': this.updateStickySteps(rect, viewportHeight, scrollY); break; } } /** * Update parallax animation */ updateParallax(rect, scrollY) { const elementTop = rect.top + scrollY; const scrolled = scrollY - elementTop; const translateY = scrolled * this.config.speed; this.element.style.transform = `translateY(${translateY}px)`; } /** * Update sticky fade animation */ updateStickyFade(rect, viewportHeight) { const progress = Math.max(0, Math.min(1, (viewportHeight - rect.top) / viewportHeight)); const opacity = this.config.fadeStart + (this.config.fadeEnd - this.config.fadeStart) * progress; this.element.style.opacity = opacity.toString(); } /** * Update sticky steps animation */ updateStickySteps(rect, viewportHeight, scrollY) { const elementTop = rect.top + scrollY - viewportHeight; const scrollProgress = Math.max(0, Math.min(1, scrollY / (rect.height + viewportHeight))); const step = Math.floor(scrollProgress * this.config.steps); this.element.setAttribute('data-step', step.toString()); this.element.classList.remove('step-0', 'step-1', 'step-2', 'step-3', 'step-4', 'step-5'); this.element.classList.add(`step-${step}`); } /** * Destroy animation */ destroy() { // Reset styles this.element.style.opacity = ''; this.element.style.transform = ''; this.element.style.transition = ''; this.element.style.transitionDelay = ''; this.element.style.position = ''; this.element.style.top = ''; // Remove classes this.element.classList.remove('visible', 'entered'); this.triggered = false; } }