1- if ( ! isMobile ) {
2-
31const hoverShift = document . querySelectorAll ( 'button, a, .article-nav-bottom, footer' ) ;
42const alwaysShift = document . querySelectorAll ( 'header, #site-title, #toolbar, nav .col a, .article-header, .article-title, .section-nav, footer' ) ;
53
64function getRandomDegree ( ) {
7- // More uniform distribution and efficient rounding
85 return Math . random ( ) < 0.5
96 ? - 45 - Math . floor ( Math . random ( ) * 270 )
107 : 46 + Math . floor ( Math . random ( ) * 224 ) ;
@@ -48,72 +45,99 @@ function startShift(element, interval, isHover = false) {
4845 element . style . filter = filterValue ;
4946 }
5047
51- function animate ( time ) {
48+ function animateColors ( time ) {
5249 if ( ! isRunning ) return ;
5350 if ( isWindowActive && isElementInViewport ( element ) ) {
5451 const deltaTime = time - lastTime ;
5552 lastTime = time ;
5653 progress += deltaTime / interval ;
57-
5854 if ( progress >= 1 ) {
5955 targetDegree = getRandomDegree ( ) ;
6056 targetSaturation = Math . random ( ) * 35 + 90 ; // 90-125
6157 targetContrast = Math . random ( ) * ( isHover ? 90 : 40 ) + 80 ;
6258 targetBrightness = Math . random ( ) * ( isHover ? 50 : 20 ) + ( isHover ? 85 : 90 ) ;
6359 progress = 0 ;
6460 }
65-
6661 const t = ( ) => metaRecursiveEaseNoise ( progress ) ;
67-
6862 currentDegree += Math . round ( ( targetDegree - currentDegree ) * t ( ) * Math . random ( ) * 0.07 ) ;
6963 currentSaturation += Math . round ( ( targetSaturation - currentSaturation ) * t ( ) * Math . random ( ) ) ;
7064 currentContrast += Math . round ( ( targetContrast - currentContrast ) * t ( ) * Math . random ( ) ) ;
7165 currentBrightness += Math . round ( ( targetBrightness - currentBrightness ) * t ( ) * Math . random ( ) ) ;
72-
7366 updateFilter ( ) ;
7467 }
75-
76- if ( isRunning ) requestAnimationFrame ( animate ) ;
68+ if ( isRunning ) requestAnimationFrame ( animateColors ) ;
7769 }
7870
79- requestAnimationFrame ( animate ) ;
80-
71+ // Initial filter apply (starts from random state)
72+ updateFilter ( ) ;
73+ requestAnimationFrame ( animateColors ) ;
8174 return ( ) => {
8275 isRunning = false ;
8376 element . style . filter = 'none' ;
8477 } ;
8578}
8679
87- function handleDisengage ( element , stopAnimationFunction ) {
88- if ( stopAnimationFunction ) {
89- stopAnimationFunction ( ) ;
80+ function handleDisengage ( element , stopAnim ) {
81+ if ( stopAnim ) {
82+ stopAnim ( ) ;
9083 }
9184 element . style . filter = 'none' ;
9285}
9386
94- // Initialize alwaysShift elements with throttled animations
95- alwaysShift . forEach ( element => {
96- element . animateFunction = throttle ( ( ) => startShift ( element , getRandomInterval ( ) ) , 30 ) ( ) ;
87+ // Viewport-lazy with light staggering: Observe in batches for gradual init
88+ const observerOptions = {
89+ threshold : 0 ,
90+ rootMargin : '200px' ,
91+ } ;
92+
93+ const animationObserver = new IntersectionObserver ( ( entries ) => {
94+ entries . forEach ( entry => {
95+ const element = entry . target ;
96+ if ( entry . isIntersecting && ! element . _isAnimating ) {
97+ element . _stopper = startShift ( element , getRandomInterval ( ) ) ;
98+ element . _isAnimating = true ;
99+ }
100+ } ) ;
101+ } , observerOptions ) ;
102+
103+ // Stagger .observe() calls lightly (~20ms batches) for perf ramp-up
104+ let observeDelay = 0 ;
105+ const observeBatchSize = 10 ;
106+ alwaysShift . forEach ( ( element , index ) => {
107+ setTimeout ( ( ) => {
108+ animationObserver . observe ( element ) ;
109+ } , observeDelay ) ;
110+ if ( index % observeBatchSize === observeBatchSize - 1 ) {
111+ observeDelay += 20 ; // Tune: 10-50ms for balance
112+ }
97113} ) ;
98114
99- // Initialize hoverShift elements with event handlers and throttle/debounce control
100- hoverShift . forEach ( element => {
101- let stopAnimationFunction = null ;
102- let disengageTimeout = null ;
103-
104- const startAnim = throttle ( ( ) => {
105- if ( stopAnimationFunction ) stopAnimationFunction ( ) ;
106- stopAnimationFunction = startShift ( element , getRandomInterval ( ) , true ) ;
107- } , 30 ) ;
108-
109- element . addEventListener ( 'mouseover' , startAnim ) ;
110- element . addEventListener ( 'click' , startAnim ) ;
111- element . addEventListener ( 'touchstart' , ( ) => {
112- if ( stopAnimationFunction ) stopAnimationFunction ( ) ;
113- stopAnimationFunction = startShift ( element , getRandomInterval ( ) , true ) ;
114- disengageTimeout = setTimeout ( ( ) => handleDisengage ( element , stopAnimationFunction ) , 888 ) ;
115+ // Cleanup on unload
116+ window . addEventListener ( 'beforeunload' , ( ) => {
117+ alwaysShift . forEach ( element => {
118+ if ( element . _stopper ) {
119+ element . _stopper ( ) ;
120+ element . _stopper = null ;
121+ }
115122 } ) ;
116- element . addEventListener ( 'mouseout' , debounce ( ( ) => handleDisengage ( element , stopAnimationFunction ) , 30 ) ) ;
123+ animationObserver . disconnect ( ) ;
117124} ) ;
118125
119- }
126+ if ( ! isMobile ) {
127+ hoverShift . forEach ( element => {
128+ let stopAnim = null ;
129+ let disengageTimeout = null ;
130+ const startAnim = throttle ( ( ) => {
131+ if ( stopAnim ) stopAnim ( ) ;
132+ stopAnim = startShift ( element , getRandomInterval ( ) , true ) ;
133+ } , 30 ) ;
134+ element . addEventListener ( 'mouseover' , startAnim ) ;
135+ element . addEventListener ( 'click' , startAnim ) ;
136+ element . addEventListener ( 'touchstart' , ( ) => {
137+ if ( stopAnim ) stopAnim ( ) ;
138+ stopAnim = startShift ( element , getRandomInterval ( ) , true ) ;
139+ disengageTimeout = setTimeout ( ( ) => handleDisengage ( element , stopAnim ) , 888 ) ;
140+ } ) ;
141+ element . addEventListener ( 'mouseout' , debounce ( ( ) => handleDisengage ( element , stopAnim ) , 30 ) ) ;
142+ } ) ;
143+ }
0 commit comments