@@ -46,14 +46,6 @@ class _StackableCarousel {
4646
4747 this . fixChildrenAccessibility ( ) // This needs to be first before infinte scrolling clones slides.
4848 this . initProperties ( )
49- this . addEventListeners ( )
50- this . fixAccessibility ( this . currentSlide )
51- this . setDotActive ( this . currentSlide )
52- this . fixInlineScrollNavigation ( )
53-
54- this . slideEls [ this . currentSlide - 1 ] . classList . add ( 'stk-block-carousel__slide--active' )
55-
56- this . unpauseAutoplay ( )
5749 }
5850
5951 initProperties = ( ) => {
@@ -73,29 +65,91 @@ class _StackableCarousel {
7365 }
7466 }
7567
68+ // If we have infiniteScroll, call this after cloning the slides
69+ const otherInitCalls = ( ) => {
70+ this . addEventListeners ( )
71+ this . fixAccessibility ( this . currentSlide )
72+ this . setDotActive ( this . currentSlide )
73+ this . fixInlineScrollNavigation ( )
74+
75+ this . slideEls [ this . currentSlide - 1 ] . classList . add ( 'stk-block-carousel__slide--active' )
76+
77+ this . unpauseAutoplay ( )
78+ }
79+
7680 if ( this . infiniteScroll && ! this . el . _StackableHasInitCarousel ) {
7781 // clone slides
78- this . clones = this . slideEls . map ( node => node . cloneNode ( true ) )
82+ this . clones = [ ]
83+ const clonesToAdd = [ ]
84+ let lastClone = null
85+ let frame = 0
86+
87+ const runInitSteps = ( ) => {
88+ if ( frame === 0 ) {
89+ this . slideEls . forEach ( ( original , i ) => {
90+ const clone = original . cloneNode ( true )
91+ clone . classList . add ( `stk-slide-clone-${ i + 1 } ` )
92+
93+ this . clones . push ( clone )
94+
95+ // Ensure click events on cloned slides are delegated to the corresponding original slide elements.
96+ // This preserves expected interactivity for cloned slides in infinite scroll.
97+ clone . addEventListener ( 'click' , e => {
98+ const targetClassList = [ ...e . target . classList ]
99+ if ( targetClassList . length ) {
100+ const targetClasses = `.${ targetClassList . join ( '.' ) } `
101+ const originalTarget = original . querySelector ( targetClasses )
102+ originalTarget . click ( )
103+ }
104+ } )
105+
106+ if ( i === this . slideEls . length - 1 ) {
107+ lastClone = clone
108+
109+ // Also add the last slide clone at the end
110+ if ( this . slidesToShow === this . slideEls . length ) {
111+ const lastCloneClone = clone . cloneNode ( true )
112+ lastCloneClone . classList . add ( `stk-slide-clone-${ i + 1 } -clone` )
113+ clonesToAdd . push ( lastCloneClone )
114+ }
115+ } else {
116+ clonesToAdd . push ( clone )
117+ }
118+ } )
119+ } else if ( frame === 2 ) {
120+ // Append clones at the end except for the last slide clone which will be placed at the front
121+ this . sliderEl . appendChild ( ...clonesToAdd )
122+ if ( lastClone ) {
123+ this . sliderEl . insertBefore ( lastClone , this . slideEls [ 0 ] )
124+ }
125+ } else if ( frame === 3 ) {
126+ // IMPORTANT: Do style reads before applying style change to improve performance
127+ // https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing
128+ const targetOffsetLeft = this . slideEls [ 0 ] . offsetLeft
129+
130+ // Scroll without animation to the first slide
131+ this . sliderEl . style . scrollBehavior = 'unset'
132+ this . sliderEl . scrollLeft = targetOffsetLeft
133+ this . sliderEl . style . scrollBehavior = ''
79134
80- this . clones . map ( ( node , i ) => {
81- node . classList . add ( `stk-slide-clone- ${ i + 1 } ` )
82- if ( i === this . clones . length - 1 ) {
83- return this . sliderEl . insertBefore ( node , this . slideEls [ 0 ] )
135+ this . currentSlide = 1
136+ this . updateDots ( )
137+ } else if ( frame === 4 ) {
138+ otherInitCalls ( )
84139 }
85140
86- return this . sliderEl . appendChild ( node )
87- } )
88-
89- // Scroll without animation to the first slide
90- this . sliderEl . style . scrollBehavior = 'unset'
91- this . sliderEl . scrollLeft = this . slideEls [ 0 ] . offsetLeft
92- this . sliderEl . style . scrollBehavior = ''
141+ frame ++
142+ if ( frame < 5 ) {
143+ requestAnimationFrame ( runInitSteps )
144+ }
145+ }
93146
94- this . currentSlide = 1
95- this . swappedSlides = 0
147+ requestAnimationFrame ( runInitSteps )
148+ return
96149 }
97150
98151 this . updateDots ( )
152+ otherInitCalls ( )
99153 }
100154
101155 updateDots = ( ) => {
@@ -198,7 +252,7 @@ class _StackableCarousel {
198252 nextSlide = ( ) => {
199253 let newSlide = this . currentSlide + 1
200254
201- if ( this . infiniteScroll && newSlide > this . maxSlides ( ) ) {
255+ if ( this . infiniteScroll && newSlide > this . slideEls . length ) {
202256 this . swapSlides ( newSlide , 'N' )
203257 return
204258 }
@@ -212,8 +266,7 @@ class _StackableCarousel {
212266 prevSlide = ( ) => {
213267 let newSlide = this . currentSlide - 1
214268
215- if ( this . infiniteScroll &&
216- ( newSlide < this . slideOffset || this . needToSwapCount ( newSlide ) >= 0 ) ) {
269+ if ( this . infiniteScroll && newSlide < this . slideOffset ) {
217270 this . swapSlides ( newSlide , 'P' )
218271 return
219272 }
@@ -225,70 +278,43 @@ class _StackableCarousel {
225278 }
226279
227280 swapSlides = ( slide , dir ) => {
228- let setScrollToClone = false
229- if ( this . slidesToShow === this . slideEls . length ) {
230- setScrollToClone = true
231- }
232-
233- if ( dir === 'N' && slide > this . slideEls . length ) {
281+ if ( dir === 'N' ) {
234282 slide = this . slideOffset
235- setScrollToClone = true
236- } else if ( dir === 'P' && slide < this . slideOffset ) {
283+ } else {
237284 slide = this . slideEls . length
238- setScrollToClone = true
239285 }
240286
241- const needToSwap = this . needToSwapCount ( slide )
242- if ( needToSwap > 0 && this . swappedSlides < needToSwap ) {
243- // swap original and clone slides
244- const original = [ ...this . slideEls . slice ( this . swappedSlides , needToSwap ) ]
245- const clones = [ ...this . clones . slice ( this . swappedSlides , needToSwap ) ]
287+ let steps = 0
246288
247- original . map ( node => this . sliderEl . insertBefore ( node , this . clones [ needToSwap ] ) )
248- clones . map ( node => this . sliderEl . insertBefore ( node , this . slideEls [ needToSwap ] ) )
289+ const runSteps = ( ) => {
290+ if ( steps === 0 ) {
291+ const lastCloneSlide = this . clones [ this . currentSlide - 1 ] . offsetLeft
292+ const firstCloneSide = this . clones [ 0 ] . offsetLeft
249293
250- // This ensures that the cloned slides are in the right position when slides to show === number of slides
251- if ( this . slidesToShow === this . slideEls . length && dir === 'N' ) {
252- const children = this . sliderEl . children
253- this . sliderEl . append ( children [ 0 ] )
254- } else if ( this . slidesToShow === this . slideEls . length && dir === 'P' ) {
255- const children = [ ...Array . from ( this . sliderEl . children ) . slice ( - 2 ) ] . reverse ( )
256- children . map ( node => this . sliderEl . insertBefore ( node , this . sliderEl . children [ 0 ] ) )
257- }
294+ let initSlide = null
258295
259- this . swappedSlides = needToSwap
260- } else if ( this . swappedSlides > needToSwap ) {
261- // unswap original and clone slides that are not needed
262- const _needToSwap = needToSwap > 0 ? needToSwap : 0
263- const original = [ ...this . slideEls . slice ( _needToSwap , this . swappedSlides ) ]
264- const clones = [ ...this . clones . slice ( _needToSwap , this . swappedSlides ) ]
265- original . map ( node => this . sliderEl . insertBefore ( node , this . slideEls [ this . swappedSlides ] ) )
266- clones . map ( node => this . sliderEl . insertBefore ( node , this . clones [ this . swappedSlides ] ) )
267- this . swappedSlides = _needToSwap
268-
269- // This ensures that the cloned slides are in the right position when slides to show === number of slides
270- if ( this . slidesToShow === this . slideEls . length ) {
271- const children = this . sliderEl . children
272- this . sliderEl . insertBefore ( children [ children . length - 1 ] , children [ 0 ] )
273- }
274- }
296+ if ( dir === 'N' ) {
297+ initSlide = lastCloneSlide
298+ } else if ( this . slidesToShow === 1 ) {
299+ initSlide = lastCloneSlide
300+ } else {
301+ initSlide = firstCloneSide
302+ }
275303
276- if ( setScrollToClone ) {
277- // Move from the last slide to the first slide (N - next) or
278- // Move from the first slide to the last slide (P - prev)
279- this . sliderEl . style . scrollBehavior = 'unset'
280- this . sliderEl . scrollLeft = dir === 'N '
281- ? this . clones [ this . currentSlide - 1 ] . offsetLeft // Go to the last clone slide
282- : ( this . slidesToShow === 1
283- ? this . clones [ this . currentSlide - 1 ] . offsetLeft // If slidesToShow is 1, go to the first clone slide
284- : this . slideEls [ this . currentSlide - 1 ] . offsetLeft // Go to the original first slide which is swapped with the clone
285- )
286- this . sliderEl . style . scrollBehavior = ''
304+ // Move from the last slide to the first slide (N - next) or
305+ // Move from the first slide to the last slide (P - prev)
306+ this . sliderEl . style . scrollBehavior = 'unset'
307+ this . sliderEl . scrollLeft = initSlide
308+ this . sliderEl . style . scrollBehavior = ' '
309+
310+ steps ++
311+ requestAnimationFrame ( runSteps )
312+ } else {
313+ this . goToSlide ( slide )
314+ }
287315 }
288316
289- setTimeout ( ( ) => {
290- this . goToSlide ( slide )
291- } , 1 )
317+ runSteps ( )
292318 }
293319
294320 goToSlide = ( slide , force = false ) => {
@@ -300,7 +326,8 @@ class _StackableCarousel {
300326 this . slideEls [ slide - 1 ] . classList . add ( 'stk-block-carousel__slide--active' )
301327
302328 if ( this . type === 'slide' ) {
303- this . sliderEl . scrollLeft = this . slideEls [ slide - 1 ] . offsetLeft
329+ const offsetLeft = this . slideEls [ slide - 1 ] . offsetLeft
330+ this . sliderEl . scrollLeft = offsetLeft
304331 } else if ( this . type === 'fade' ) { // fade
305332 const slidePrevEl = this . slideEls [ this . currentSlide - 1 ]
306333 slidePrevEl . style . opacity = 0
@@ -414,6 +441,9 @@ class _StackableCarousel {
414441 }
415442
416443 onWheel = e => {
444+ const sliderElScrollLeft = this . sliderEl . scrollLeft
445+ const lastSlideOffset = this . slideEls [ this . slideEls . length - 1 ] . offsetLeft
446+ const firstCloneOffset = this . clones [ 0 ] . offsetLeft
417447 if ( this . type === 'fade' ) {
418448 if ( this . wheelTimeout ) {
419449 return
@@ -430,15 +460,17 @@ class _StackableCarousel {
430460 } , 500 )
431461 }
432462 // For infinite scrolling, set the scroll position to the actual slide ( not to the clone of the slide )
433- } else if ( this . infiniteScroll && e . deltaX <= - 1 && this . sliderEl . scrollLeft === 0 ) {
463+ } else if ( this . infiniteScroll && e . deltaX <= - 1 && sliderElScrollLeft === 0 ) {
434464 this . sliderEl . style . scrollBehavior = 'unset'
435- this . sliderEl . scrollLeft = this . slideEls [ this . slideEls . length - 1 ] . offsetLeft
465+ this . sliderEl . scrollLeft = lastSlideOffset
436466 this . sliderEl . style . scrollBehavior = ''
437- } else if ( this . infiniteScroll && e . deltaX >= 1 && this . sliderEl . scrollLeft >= this . clones [ 0 ] . offsetLeft ) {
467+ } else if ( this . infiniteScroll && e . deltaX >= 1 && sliderElScrollLeft >= firstCloneOffset ) {
438468 this . clones . every ( ( clone , i ) => {
439- if ( this . sliderEl . scrollLeft === clone . offsetLeft ) {
469+ const cloneOffset = clone . offsetLeft
470+ const slideOffset = this . slideEls [ i ] . offsetLeft
471+ if ( sliderElScrollLeft === cloneOffset ) {
440472 this . sliderEl . style . scrollBehavior = 'unset'
441- this . sliderEl . scrollLeft = this . slideEls [ i ] . offsetLeft
473+ this . sliderEl . scrollLeft = slideOffset
442474 this . sliderEl . style . scrollBehavior = ''
443475 return false
444476 }
@@ -467,14 +499,17 @@ class _StackableCarousel {
467499 dragMouseMove = e => {
468500 // How far the mouse has been moved
469501 let dx = e . clientX - this . initialClientX
502+ const sliderElScrollLeft = this . sliderEl . scrollLeft
503+ const lastSlideOffsetLeft = this . slideEls [ this . slideEls . length - 1 ] . offsetLeft
504+ const firstCloneOffsetLeft = this . clones [ 0 ] . offsetLeft
470505
471506 if ( this . type === 'slide' ) {
472- if ( this . infiniteScroll && this . sliderEl . scrollLeft === 0 && dx > 0 ) {
473- this . initialScrollLeft = this . slideEls [ this . slideEls . length - 1 ] . offsetLeft
507+ if ( this . infiniteScroll && sliderElScrollLeft === 0 && dx > 0 ) {
508+ this . initialScrollLeft = lastSlideOffsetLeft
474509 this . initialClientX = e . clientX
475510 dx = 0
476- } else if ( this . infiniteScroll && this . sliderEl . scrollLeft >= this . clones [ 0 ] . offsetLeft && dx < 0 ) {
477- this . initialScrollLeft = this . slideEls [ 0 ] . offsetLeft
511+ } else if ( this . infiniteScroll && sliderElScrollLeft >= firstCloneOffsetLeft && dx < 0 ) {
512+ this . initialScrollLeft = firstCloneOffsetLeft
478513 this . initialClientX = e . clientX
479514 dx = 0
480515 }
0 commit comments