1/** 2 * Fade Controller - Manages container opacity transitions 3 */ 4 5class FadeController { 6 constructor(element) { 7 this.element = typeof element === 'string' ? document.querySelector(element) : element; 8 this.currentOpacity = 0; 9 this.targetOpacity = 0; 10 this.duration = 0; 11 this.startTime = null; 12 this.easing = this.easeInOutCubic; 13 } 14 15 fadeIn(duration = 1500) { 16 this.animate(1, duration); 17 } 18 19 fadeOut(duration = 1500) { 20 this.animate(0, duration); 21 } 22 23 setOpacity(target, duration = 0) { 24 this.animate(target, duration); 25 } 26 27 animate(target, duration) { 28 // Sync from the element's actual current opacity before starting a new 29 // interpolation. this.currentOpacity is otherwise just an internal 30 // tracker that only ever gets updated by this class's OWN previous 31 // writes (starts at 0 from the constructor) â if the element's real 32 // opacity is different (its CSS/HTML default, or something else set it 33 // directly), the first animation frame below would snap the element to 34 // the stale tracked value before continuing toward target, instead of 35 // starting from wherever it visually already is. Confirmed cause of 36 // "particles abruptly disappear, then fade back in" on the very first 37 // fadeIn() call of a page load â the element defaulted to opacity 1 38 // (untouched), this.currentOpacity was still its constructor default 39 // of 0, so the first frame here forced a visible snap to 0. 40 const computed = parseFloat(getComputedStyle(this.element).opacity); 41 if (!Number.isNaN(computed)) this.currentOpacity = computed; 42 43 this.targetOpacity = target; 44 this.duration = duration; 45 this.startTime = Date.now(); 46 47 if (duration === 0) { 48 this.currentOpacity = target; 49 this.element.style.opacity = target.toString(); 50 } else { 51 this.updateFrame(); 52 } 53 } 54 55 updateFrame() { 56 if (!this.startTime) return; 57 58 const elapsed = Date.now() - this.startTime; 59 const progress = Math.min(1, elapsed / this.duration); 60 const eased = this.easing(progress); 61 62 this.currentOpacity = this.currentOpacity + (this.targetOpacity - this.currentOpacity) * eased; 63 this.element.style.opacity = Math.max(0, Math.min(1, this.currentOpacity)).toString(); 64 65 if (progress < 1) { 66 requestAnimationFrame(() => this.updateFrame()); 67 } 68 } 69 70 easeInOutCubic(t) { 71 return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; 72 } 73} 74 75// Export for browser 76if (typeof window !== 'undefined') { 77 window.FadeController = FadeController; 78}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.