/** * Timeline Animation * * Handles scroll timeline animations (scroll-timeline, scroll-loop) */ import { Logger } from '../../core/logger.js'; /** * TimelineAnimation - Scroll timeline animation */ export class TimelineAnimation { constructor(element, config = {}) { this.element = element; this.config = { steps: config.steps || null, triggerPoint: config.triggerPoint || 0.4, loop: config.loop ?? false, ...config }; this.currentStep = 0; this.triggered = false; // Initialize this.init(); } /** * Initialize timeline animation */ init() { // Set initial step this.element.setAttribute('data-scroll-step', '0'); this.element.classList.add('scroll-timeline'); } /** * Enter animation */ enter() { if (this.triggered && !this.config.loop) { return; } this.triggered = true; this.update(); } /** * Exit animation */ exit() { if (this.config.loop) { this.currentStep = 0; this.update(); } } /** * Update animation based on scroll position */ update() { const rect = this.element.getBoundingClientRect(); const viewportHeight = window.innerHeight; const triggerY = viewportHeight * this.config.triggerPoint; if (rect.top < triggerY && rect.bottom > 0) { // Calculate progress const progress = Math.max(0, Math.min(1, (triggerY - rect.top) / (rect.height + viewportHeight))); if (this.config.steps) { // Step-based animation const step = Math.floor(progress * this.config.steps); this.setStep(step); } else { // Continuous animation this.setProgress(progress); } } } /** * Set step */ setStep(step) { if (step === this.currentStep) { return; } this.currentStep = step; this.element.setAttribute('data-scroll-step', step.toString()); // Remove old step classes for (let i = 0; i <= this.config.steps; i++) { this.element.classList.remove(`step-${i}`); } // Add new step class this.element.classList.add(`step-${step}`); // Trigger event this.triggerStepEvent(step); } /** * Set progress (continuous) */ setProgress(progress) { this.element.setAttribute('data-scroll-progress', progress.toString()); this.element.style.setProperty('--scroll-progress', progress.toString()); // Trigger event this.triggerProgressEvent(progress); } /** * Trigger step event */ triggerStepEvent(step) { const event = new CustomEvent('scroll-timeline:step', { detail: { step, element: this.element }, bubbles: true }); this.element.dispatchEvent(event); } /** * Trigger progress event */ triggerProgressEvent(progress) { const event = new CustomEvent('scroll-timeline:progress', { detail: { progress, element: this.element }, bubbles: true }); this.element.dispatchEvent(event); } /** * Destroy animation */ destroy() { this.element.removeAttribute('data-scroll-step'); this.element.removeAttribute('data-scroll-progress'); this.element.style.removeProperty('--scroll-progress'); this.element.classList.remove('scroll-timeline'); // Remove step classes for (let i = 0; i <= 10; i++) { this.element.classList.remove(`step-${i}`); } this.currentStep = 0; this.triggered = false; } }