11import { Component } from "@ribajs/core" ;
22import { EventDispatcher } from "@ribajs/events" ;
3+ import { debounceCb , debounceF } from "@ribajs/utils/src/control.js" ;
34import { hasChildNodesTrim } from "@ribajs/utils/src/dom.js" ;
45
56import templateHtml from "./dfw-background-gears.component.html?raw" ;
@@ -34,13 +35,16 @@ export class DfwBackgroundGearsComponent extends Component {
3435
3536 protected autobind = true ;
3637
37- private shineRafId : number | null = null ;
3838 private resizeObserver : ResizeObserver | null = null ;
39+ private static readonly RESIZE_DEBOUNCE_MS = 450 ;
3940 private routerUnsubscribe : ( ( ) => void ) | null = null ;
40- private readonly boundUpdateShineFromScroll = ( ) => this . updateShineFromScroll ( ) ;
41+ private debouncedShineFromScroll ! : ( ) => void ;
42+ private debouncedShineFromPointer ! : ( e : MouseEvent ) => void ;
43+ private readonly boundUpdateShineFromScroll = ( ) => this . debouncedShineFromScroll ( ) ;
4144 private readonly boundUpdateShineFromPointer = ( e : MouseEvent ) =>
42- this . updateShineFromPointer ( e ) ;
43- private readonly boundUpdateHeightToPage = ( ) => this . updateHeightToPage ( ) ;
45+ this . debouncedShineFromPointer ( e ) ;
46+ private readonly boundOnResize = ( ) => this . onResize ( ) ;
47+ private debouncedResizeDone ! : ( ) => void ;
4448 private readonly boundOnTransitionCompleted = ( ) => this . onTransitionCompleted ( ) ;
4549 private readonly boundOnInitStateChange = (
4650 _viewId : string ,
@@ -59,15 +63,30 @@ export class DfwBackgroundGearsComponent extends Component {
5963 { src : gear2Url , speed : 0.32 , class : "bg-gear--5" , maskStyle : { "--gear-mask" : `url("${ gear2Url } ")` } } ,
6064 ] as const ;
6165
62- /** Set gears container height to full page height so gear positions (top: X%) distribute along the page. */
66+ /** Hide gears and schedule debounced repositioning when viewport size changes. */
67+ private onResize ( ) : void {
68+ ( this as unknown as HTMLElement ) . classList . add ( "dfw-background-gears--resizing" ) ;
69+ this . debouncedResizeDone ( ) ;
70+ }
71+
72+ /** Set gears container height to content height so gear positions (top: X%) distribute along the page.
73+ * Height is read while gears container is collapsed to avoid the gears contributing to document height. */
6374 private updateHeightToPage ( ) : void {
75+ const container = this . parentElement as HTMLElement | null ;
76+ const self = this as unknown as HTMLElement ;
77+
78+ if ( container ?. classList . contains ( "background-gears" ) ) {
79+ container . style . height = "0" ;
80+ }
81+ self . style . height = "0" ;
82+
6483 const h = document . documentElement . scrollHeight ;
6584 const px = `${ h } px` ;
66- const container = this . parentElement ;
85+
6786 if ( container ?. classList . contains ( "background-gears" ) ) {
68- ( container as HTMLElement ) . style . height = px ;
87+ container . style . height = px ;
6988 }
70- ( this as unknown as HTMLElement ) . style . height = px ;
89+ self . style . height = px ;
7190 }
7291
7392 /** Collapse gears container and remove gear nodes so document height is not preserved on page change. */
@@ -130,39 +149,44 @@ export class DfwBackgroundGearsComponent extends Component {
130149 document . documentElement . style . setProperty ( "--shine-y" , y ) ;
131150 }
132151
133- private updateShineFromScroll ( ) : void {
134- if ( this . shineRafId !== null ) return ;
135- this . shineRafId = requestAnimationFrame ( ( ) => {
136- this . shineRafId = null ;
137- const scrollY = window . scrollY ?? document . documentElement . scrollTop ;
138- const innerHeight = window . innerHeight ;
139- const scrollHeight = document . documentElement . scrollHeight ;
140- const maxScroll = Math . max ( 0 , scrollHeight - innerHeight ) ;
141- const progress = maxScroll > 0 ? scrollY / maxScroll : 0 ;
142- const cycles = 3 ;
143- const phase = ( progress * cycles ) % 1 ;
144- const yPercent =
145- phase <= 0.5 ? phase * 200 : ( 1 - phase ) * 200 ;
146- this . setShine ( this . shineX , `${ Math . min ( 100 , Math . max ( 0 , yPercent ) ) } %` ) ;
147- } ) ;
152+ private doUpdateShineFromScroll ( ) : void {
153+ if ( ! this . isConnected ) return ;
154+ const scrollY = window . scrollY ?? document . documentElement . scrollTop ;
155+ const innerHeight = window . innerHeight ;
156+ const scrollHeight = document . documentElement . scrollHeight ;
157+ const maxScroll = Math . max ( 0 , scrollHeight - innerHeight ) ;
158+ const progress = maxScroll > 0 ? scrollY / maxScroll : 0 ;
159+ const cycles = 3 ;
160+ const phase = ( progress * cycles ) % 1 ;
161+ const yPercent =
162+ phase <= 0.5 ? phase * 200 : ( 1 - phase ) * 200 ;
163+ this . setShine ( this . shineX , `${ Math . min ( 100 , Math . max ( 0 , yPercent ) ) } %` ) ;
148164 }
149165
150- private updateShineFromPointer ( e : MouseEvent ) : void {
151- if ( this . shineRafId !== null ) return ;
152- this . shineRafId = requestAnimationFrame ( ( ) => {
153- this . shineRafId = null ;
154- const xPercent = ( e . clientX / window . innerWidth ) * 100 ;
155- this . setShine ( `${ xPercent } %` , this . shineY ) ;
156- } ) ;
166+ private doUpdateShineFromPointer ( e : MouseEvent ) : void {
167+ if ( ! this . isConnected ) return ;
168+ const xPercent = ( e . clientX / window . innerWidth ) * 100 ;
169+ this . setShine ( `${ xPercent } %` , this . shineY ) ;
157170 }
158171
159172 protected connectedCallback ( ) {
160173 super . connectedCallback ( ) ;
161174 this . init ( DfwBackgroundGearsComponent . observedAttributes ) ;
162175
176+ this . debouncedResizeDone = debounceCb ( ( ) => {
177+ if ( ! this . isConnected ) return ;
178+ ( this as unknown as HTMLElement ) . classList . remove ( "dfw-background-gears--resizing" ) ;
179+ this . updateHeightToPage ( ) ;
180+ this . scope . backgroundGears = this . buildGearsWithRandomLayout ( ) ;
181+ this . view ?. update ( this . scope ) ;
182+ } , DfwBackgroundGearsComponent . RESIZE_DEBOUNCE_MS ) ;
183+
184+ this . debouncedShineFromScroll = debounceF ( ( ) => this . doUpdateShineFromScroll ( ) ) ;
185+ this . debouncedShineFromPointer = debounceF ( ( e : MouseEvent ) => this . doUpdateShineFromPointer ( e ) ) ;
186+
163187 this . scope . backgroundGears = this . buildGearsWithRandomLayout ( ) ;
164188 this . updateHeightToPage ( ) ;
165- this . updateShineFromScroll ( ) ;
189+ this . debouncedShineFromScroll ( ) ;
166190
167191 const dispatcher = EventDispatcher . getInstance ( ROUTER_VIEW_ID ) ;
168192 dispatcher . on ( "initStateChange" , this . boundOnInitStateChange ) ;
@@ -176,8 +200,8 @@ export class DfwBackgroundGearsComponent extends Component {
176200 passive : true ,
177201 } ) ;
178202 window . addEventListener ( "resize" , this . boundUpdateShineFromScroll ) ;
179- window . addEventListener ( "resize" , this . boundUpdateHeightToPage ) ;
180- this . resizeObserver = new ResizeObserver ( ( ) => this . updateHeightToPage ( ) ) ;
203+ window . addEventListener ( "resize" , this . boundOnResize ) ;
204+ this . resizeObserver = new ResizeObserver ( this . boundOnResize ) ;
181205 this . resizeObserver . observe ( document . body ) ;
182206 if ( window . matchMedia ( "(pointer: fine)" ) . matches ) {
183207 window . addEventListener ( "mousemove" , this . boundUpdateShineFromPointer ) ;
@@ -187,15 +211,11 @@ export class DfwBackgroundGearsComponent extends Component {
187211 protected disconnectedCallback ( ) : void {
188212 this . routerUnsubscribe ?.( ) ;
189213 this . routerUnsubscribe = null ;
190- if ( this . shineRafId !== null ) {
191- cancelAnimationFrame ( this . shineRafId ) ;
192- this . shineRafId = null ;
193- }
194214 this . resizeObserver ?. disconnect ( ) ;
195215 this . resizeObserver = null ;
196216 window . removeEventListener ( "scroll" , this . boundUpdateShineFromScroll ) ;
197217 window . removeEventListener ( "resize" , this . boundUpdateShineFromScroll ) ;
198- window . removeEventListener ( "resize" , this . boundUpdateHeightToPage ) ;
218+ window . removeEventListener ( "resize" , this . boundOnResize ) ;
199219 window . removeEventListener ( "mousemove" , this . boundUpdateShineFromPointer ) ;
200220 super . disconnectedCallback ( ) ;
201221 }
0 commit comments