@@ -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,97 @@ 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 index = 0
86+ let step = 0
87+
88+ const runInitSteps = ( ) => {
89+ if ( step === 0 ) {
90+ // Clone only the last slide and the first N slides (where N equals to slidesToShow)
91+ const slideIndex = index === this . slidesToShow ? this . slideEls . length - 1 : index
92+ const original = this . slideEls [ slideIndex ]
93+ const clone = original . cloneNode ( true )
94+ clone . classList . add ( `stk-slide-clone-${ slideIndex + 1 } ` )
95+ clone . style . zIndex = - 1
96+ original . style . willChange = 'transform'
97+ original . style . transform = 'TranslateX( 0 )'
98+
99+ // prevents flickering when changing the value of TranslateX
100+ original . style . transition = 'transform 0s'
101+
102+ this . clones . push ( clone )
103+
104+ if ( index === this . slidesToShow ) {
105+ lastClone = clone
106+ step ++
107+ } else {
108+ clonesToAdd . push ( clone )
109+ }
110+
111+ index ++
112+ } else if ( step === 1 ) {
113+ // Append clones at the end except for the last slide clone which will be placed at the front
114+ this . sliderEl . append ( ...clonesToAdd )
115+ if ( lastClone ) {
116+ this . sliderEl . insertBefore ( lastClone , this . slideEls [ 0 ] )
117+ }
118+ step ++
119+ } else if ( step === 2 ) {
120+ const numSlides = this . slideEls . length
121+ const slideClientRect = this . slideEls [ 0 ] . getBoundingClientRect ( )
122+ const slideWidth = slideClientRect . width
123+
124+ this . slideTranslateX = `calc((${ slideWidth } px * ${ numSlides } ) + (var(--gap) * ${ numSlides } ))`
125+
126+ step ++
127+ } else if ( step === 3 ) {
128+ // IMPORTANT: Do style reads before applying style change to improve performance
129+ // https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing
130+ const targetOffsetLeft = this . slideEls [ 0 ] . offsetLeft
131+
132+ // Scroll without animation to the first slide
133+ this . sliderEl . style . scrollBehavior = 'unset'
134+ this . sliderEl . scrollLeft = targetOffsetLeft
135+ step ++
136+ } else if ( step === 4 ) {
137+ this . sliderEl . style . scrollBehavior = ''
79138
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 ] )
139+ this . currentSlide = 1
140+ this . swappedSlides = 0
141+ this . updateDots ( )
142+ step ++
143+ } else if ( step === 5 ) {
144+ otherInitCalls ( )
145+ step ++
84146 }
85147
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 = ''
148+ if ( step <= 5 ) {
149+ requestAnimationFrame ( runInitSteps )
150+ }
151+ }
93152
94- this . currentSlide = 1
95- this . swappedSlides = 0
153+ requestAnimationFrame ( runInitSteps )
154+ return
96155 }
97156
98157 this . updateDots ( )
158+ otherInitCalls ( )
99159 }
100160
101161 updateDots = ( ) => {
@@ -198,7 +258,7 @@ class _StackableCarousel {
198258 nextSlide = ( ) => {
199259 let newSlide = this . currentSlide + 1
200260
201- if ( this . infiniteScroll && newSlide > this . maxSlides ( ) ) {
261+ if ( this . type === 'slide' && this . infiniteScroll && newSlide > this . maxSlides ( ) ) {
202262 this . swapSlides ( newSlide , 'N' )
203263 return
204264 }
@@ -212,7 +272,7 @@ class _StackableCarousel {
212272 prevSlide = ( ) => {
213273 let newSlide = this . currentSlide - 1
214274
215- if ( this . infiniteScroll &&
275+ if ( this . type === 'slide' && this . infiniteScroll &&
216276 ( newSlide < this . slideOffset || this . needToSwapCount ( newSlide ) >= 0 ) ) {
217277 this . swapSlides ( newSlide , 'P' )
218278 return
@@ -225,95 +285,100 @@ class _StackableCarousel {
225285 }
226286
227287 swapSlides = ( slide , dir ) => {
228- let setScrollToClone = false
229- if ( this . slidesToShow === this . slideEls . length ) {
230- setScrollToClone = true
231- }
232-
288+ let scrollToSlide = false
233289 if ( dir === 'N' && slide > this . slideEls . length ) {
234290 slide = this . slideOffset
235- setScrollToClone = true
291+ scrollToSlide = true
236292 } else if ( dir === 'P' && slide < this . slideOffset ) {
237293 slide = this . slideEls . length
238- setScrollToClone = true
294+ scrollToSlide = true
239295 }
240296
241297 const needToSwap = this . needToSwapCount ( slide )
298+ let startIndex = 0
299+ let endIndex = 0
300+ let slideTranslateXValue = 0
242301 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 ) ]
246-
247- original . map ( node => this . sliderEl . insertBefore ( node , this . clones [ needToSwap ] ) )
248- clones . map ( node => this . sliderEl . insertBefore ( node , this . slideEls [ needToSwap ] ) )
249-
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- }
302+ startIndex = this . swappedSlides
303+ endIndex = needToSwap
304+
305+ slideTranslateXValue = this . slideTranslateX
258306
259- this . swappedSlides = needToSwap
307+ this . swappedSlides = endIndex
260308 } 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- }
309+ startIndex = needToSwap > 0 ? needToSwap : 0
310+ endIndex = this . swappedSlides
311+
312+ this . swappedSlides = startIndex
274313 }
275314
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 = ''
315+ let step = 0
316+
317+ const runSteps = ( ) => {
318+ if ( step === 0 ) {
319+ this . slideEls . slice ( startIndex , endIndex ) . forEach ( slide => {
320+ slide . style . transform = `TranslateX(${ slideTranslateXValue } )`
321+ } )
322+ step ++
323+ requestAnimationFrame ( runSteps )
324+ } else if ( step === 1 ) {
325+ this . slideEls . slice ( startIndex , endIndex ) . forEach ( slide => slide . offsetLeft )
326+
327+ if ( scrollToSlide ) {
328+ const lastCloneSlide = this . clones [ this . clones . length - 1 ] . offsetLeft
329+ const firstCloneSide = this . clones [ 0 ] . offsetLeft
330+
331+ let initSlide = null
332+
333+ if ( dir === 'N' ) {
334+ initSlide = lastCloneSlide
335+ } else if ( this . slidesToShow === 1 ) {
336+ initSlide = lastCloneSlide
337+ } else {
338+ initSlide = firstCloneSide
339+ }
340+
341+ // Move from the last slide to the first slide (N - next) or
342+ // Move from the first slide to the last slide (P - prev)
343+ this . sliderEl . style . scrollBehavior = 'unset'
344+ this . sliderEl . scrollLeft = initSlide
345+ this . sliderEl . style . scrollBehavior = ''
346+ }
347+
348+ step ++
349+ requestAnimationFrame ( runSteps )
350+ } else {
351+ requestAnimationFrame ( ( ) => this . goToSlide ( slide ) )
352+ }
287353 }
288354
289- setTimeout ( ( ) => {
290- this . goToSlide ( slide )
291- } , 1 )
355+ requestAnimationFrame ( runSteps )
292356 }
293357
294358 goToSlide = ( slide , force = false ) => {
295359 if ( slide === this . currentSlide && ! force ) {
296360 return
297361 }
362+ const currentSlideEl = this . slideEls [ this . currentSlide - 1 ]
363+ const newSlideEl = this . slideEls [ slide - 1 ]
364+ const offsetLeft = newSlideEl . offsetLeft
298365
299- this . slideEls [ this . currentSlide - 1 ] . classList . remove ( 'stk-block-carousel__slide--active' )
300- this . slideEls [ slide - 1 ] . classList . add ( 'stk-block-carousel__slide--active' )
366+ currentSlideEl . classList . remove ( 'stk-block-carousel__slide--active' )
367+ newSlideEl . classList . add ( 'stk-block-carousel__slide--active' )
301368
302369 if ( this . type === 'slide' ) {
303- this . sliderEl . scrollLeft = this . slideEls [ slide - 1 ] . offsetLeft
370+ this . sliderEl . scrollLeft = offsetLeft
304371 } else if ( this . type === 'fade' ) { // fade
305- const slidePrevEl = this . slideEls [ this . currentSlide - 1 ]
306- slidePrevEl . style . opacity = 0
307-
308- const slideEl = this . slideEls [ slide - 1 ]
309- slideEl . style . zIndex = ++ this . currentZIndex
310- slideEl . style . transition = 'none'
311- slideEl . style . opacity = 0
312- slideEl . style . visibility = 'visible'
313- slideEl . style . left = `${ this . isRTL ? '' : '-' } ${ 100 * ( slide - 1 ) } %`
372+ currentSlideEl . style . opacity = 0
373+
374+ newSlideEl . style . zIndex = ++ this . currentZIndex
375+ newSlideEl . style . transition = 'none'
376+ newSlideEl . style . opacity = 0
377+ newSlideEl . style . visibility = 'visible'
378+ newSlideEl . style . left = `${ this . isRTL ? '' : '-' } ${ 100 * ( slide - 1 ) } %`
314379 setTimeout ( ( ) => {
315- slideEl . style . transition = ''
316- slideEl . style . opacity = 1
380+ newSlideEl . style . transition = ''
381+ newSlideEl . style . opacity = 1
317382 } , 1 )
318383 }
319384 this . fixAccessibility ( slide )
@@ -414,6 +479,11 @@ class _StackableCarousel {
414479 }
415480
416481 onWheel = e => {
482+ const sliderElScrollLeft = this . sliderEl . scrollLeft
483+ const lastSlideOffset = this . slideEls [ this . slideEls . length - 1 ] . offsetLeft
484+ const firstCloneOffset = this . clones [ 0 ] . offsetLeft
485+ const slidesOffset = this . slideEls . map ( slide => slide . offsetLeft )
486+ const clonesOffset = this . clones . map ( slide => slide . offsetLeft )
417487 if ( this . type === 'fade' ) {
418488 if ( this . wheelTimeout ) {
419489 return
@@ -430,19 +500,19 @@ class _StackableCarousel {
430500 } , 500 )
431501 }
432502 // 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 ) {
503+ } else if ( this . infiniteScroll && e . deltaX <= - 1 && sliderElScrollLeft === 0 ) {
434504 this . sliderEl . style . scrollBehavior = 'unset'
435- this . sliderEl . scrollLeft = this . slideEls [ this . slideEls . length - 1 ] . offsetLeft
505+ this . sliderEl . scrollLeft = lastSlideOffset
436506 this . sliderEl . style . scrollBehavior = ''
437- } else if ( this . infiniteScroll && e . deltaX >= 1 && this . sliderEl . scrollLeft >= this . clones [ 0 ] . offsetLeft ) {
438- this . clones . every ( ( clone , i ) => {
439- if ( this . sliderEl . scrollLeft === clone . offsetLeft ) {
507+ } else if ( this . infiniteScroll && e . deltaX >= 1 && sliderElScrollLeft >= firstCloneOffset ) {
508+ clonesOffset . some ( ( offset , i ) => {
509+ if ( sliderElScrollLeft === offset ) {
440510 this . sliderEl . style . scrollBehavior = 'unset'
441- this . sliderEl . scrollLeft = this . slideEls [ i ] . offsetLeft
511+ this . sliderEl . scrollLeft = slidesOffset [ i ]
442512 this . sliderEl . style . scrollBehavior = ''
443- return false
513+ return true
444514 }
445- return true
515+ return false
446516 } )
447517 }
448518 }
@@ -467,14 +537,17 @@ class _StackableCarousel {
467537 dragMouseMove = e => {
468538 // How far the mouse has been moved
469539 let dx = e . clientX - this . initialClientX
540+ const sliderElScrollLeft = this . sliderEl . scrollLeft
541+ const lastSlideOffsetLeft = this . slideEls [ this . slideEls . length - 1 ] . offsetLeft
542+ const firstCloneOffsetLeft = this . clones [ 0 ] . offsetLeft
470543
471544 if ( this . type === 'slide' ) {
472- if ( this . infiniteScroll && this . sliderEl . scrollLeft === 0 && dx > 0 ) {
473- this . initialScrollLeft = this . slideEls [ this . slideEls . length - 1 ] . offsetLeft
545+ if ( this . infiniteScroll && sliderElScrollLeft === 0 && dx > 0 ) {
546+ this . initialScrollLeft = lastSlideOffsetLeft
474547 this . initialClientX = e . clientX
475548 dx = 0
476- } else if ( this . infiniteScroll && this . sliderEl . scrollLeft >= this . clones [ 0 ] . offsetLeft && dx < 0 ) {
477- this . initialScrollLeft = this . slideEls [ 0 ] . offsetLeft
549+ } else if ( this . infiniteScroll && sliderElScrollLeft >= firstCloneOffsetLeft && dx < 0 ) {
550+ this . initialScrollLeft = firstCloneOffsetLeft
478551 this . initialClientX = e . clientX
479552 dx = 0
480553 }
0 commit comments