Hi,
I am building a slider based on this example https://codepen.io/andrei-savu/pen/BaPqzvX
It works when it's alone on a page, https://yaojuilan.art/gsap
While it isn't working when there is something else https://yaojuilan.art/system_of_conductors/field-walk#kinmen (the slider works sometime. it is unstable. )
I tried logging out the observer onChange, the event does trigger, but the items just would not do the horizontal transition.
I am wondering if observer has some sort of limitation, or maybe observer listener is interfering with something?
Sorry i did not create a codepen, because this component does works standalone. Here is the slider component
export default async function Page() {
const data= await getPageContent()
return (
<div id='intro' className='relative h-auto w-full overflow-x-hidden'>
<div className='h-[50vh] w-full'> some content </div>
<Slider items={data?.carousel_img?.images} />
<div className='h-[200vh] w-full bg-red-100'> some content </div>
</div>
)
}
export default function Slider({ items, section }) {
useGSAP(() => {
let loop = horizontalLoop(`.carousel-${section} li`, { repeat: -1 })
let slow = gsap.to(loop, { timeScale: 0, duration: 0.5 })
loop?.timeScale(0)
Observer.create({
target: `.carousel-${section}`,
type: 'pointer,touch,wheel',
wheelSpeed: -1,
preventDefault: true,
onChange: (self) => {
loop.timeScale(Math.abs(self.deltaX) > Math.abs(self.deltaY) ? -self.deltaX : -self.deltaY) // whichever direction is bigger
slow.invalidate().restart() // now decelerate
},
})
})
return (
<div className='absolute bottom-12 w-full cursor-grab overflow-hidden'>
<ul className={`carousel-${section} carousel flex flex-nowrap pl-0`}>
{items?.map((item, i) => (
<li key={i}>
<Image
alt={'collective of images'}
src={item}
width={150}
height={150}
sizes='100vw'
className='pointer-events-none touch-none select-none '
/>
</li>
))}
</ul>
</div>
)
}
function horizontalLoop(items, config) {
items = gsap.utils.toArray(items)
if (!items.length) return
config = config || {}
let tl = gsap.timeline({
repeat: config.repeat,
paused: config.paused,
defaults: { ease: 'none' },
onReverseComplete: () => tl.totalTime(tl.rawTime() + tl.duration() * 100),
}),
length = items.length,
startX = items[0].offsetLeft,
times = [],
widths = [],
xPercents = [],
curIndex = 0,
pixelsPerSecond = (config.speed || 1) * 100,
snap = config.snap === false ? (v) => v : gsap.utils.snap(config.snap || 1), // some browsers shift by a pixel to accommodate flex layouts, so for example if width is 20% the first element's width might be 242px, and the next 243px, alternating back and forth. So we snap to 5 percentage points to make things look more natural
totalWidth,
curX,
distanceToStart,
distanceToLoop,
item,
i
gsap.set(items, {
// convert "x" to "xPercent" to make things responsive, and populate the widths/xPercents Arrays to make lookups faster.
xPercent: (i, el) => {
let w = (widths[i] = parseFloat(gsap.getProperty(el, 'width', 'px')))
xPercents[i] = snap((parseFloat(gsap.getProperty(el, 'x', 'px')) / w) * 100 + gsap.getProperty(el, 'xPercent'))
return xPercents[i]
},
})
gsap.set(items, { x: 0 })
totalWidth =
items[length - 1].offsetLeft +
(xPercents[length - 1] / 100) * widths[length - 1] -
startX +
items[length - 1].offsetWidth * gsap.getProperty(items[length - 1], 'scaleX') +
(parseFloat(config.paddingRight) || 0)
for (i = 0; i < length; i++) {
item = items[i]
curX = (xPercents[i] / 100) * widths[i]
distanceToStart = item.offsetLeft + curX - startX
distanceToLoop = distanceToStart + widths[i] * gsap.getProperty(item, 'scaleX')
tl.to(
item,
{ xPercent: snap(((curX - distanceToLoop) / widths[i]) * 100), duration: distanceToLoop / pixelsPerSecond },
0,
)
.fromTo(
item,
{ xPercent: snap(((curX - distanceToLoop + totalWidth) / widths[i]) * 100) },
{
xPercent: xPercents[i],
duration: (curX - distanceToLoop + totalWidth - curX) / pixelsPerSecond,
immediateRender: false,
},
distanceToLoop / pixelsPerSecond,
)
.add('label' + i, distanceToStart / pixelsPerSecond)
times[i] = distanceToStart / pixelsPerSecond
}
function toIndex(index, vars) {
vars = vars || {}
Math.abs(index - curIndex) > length / 2 && (index += index > curIndex ? -length : length) // always go in the shortest direction
let newIndex = gsap.utils.wrap(0, length, index),
time = times[newIndex]
if (time > tl.time() !== index > curIndex) {
// if we're wrapping the timeline's playhead, make the proper adjustments
vars.modifiers = { time: gsap.utils.wrap(0, tl.duration()) }
time += tl.duration() * (index > curIndex ? 1 : -1)
}
curIndex = newIndex
vars.overwrite = true
return tl.tweenTo(time, vars)
}
tl.next = (vars) => toIndex(curIndex + 1, vars)
tl.previous = (vars) => toIndex(curIndex - 1, vars)
tl.current = () => curIndex
tl.toIndex = (index, vars) => toIndex(index, vars)
tl.times = times
tl.progress(1, true).progress(0, true) // pre-render for performance
if (config.reversed) {
tl.vars.onReverseComplete()
tl.reverse()
}
return tl
}