Skip to content

Commit 54f6ea8

Browse files
committed
improve carousel frontend performance
removed swapping of slides
1 parent cbd689f commit 54f6ea8

File tree

1 file changed

+124
-89
lines changed

1 file changed

+124
-89
lines changed

src/block/carousel/frontend-carousel.js

Lines changed: 124 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)