Jump to content
Search Community

Horizontal slider with Observer

LuLUE7775 test
Moderator Tag

Recommended Posts

Hi, 

I am building a slider based on this example 

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
}

See the Pen BaPqzvX by andrei-savu (@andrei-savu) on CodePen

Link to comment
Share on other sites

Without a minimal demo, it's very difficult to troubleshoot; the issue could be caused by CSS, markup, a third party library, a 3rd party script, etc. Would you please provide a very simple CodePen or Stackblitz that illustrates the issue? 

 

Using a framework/library like React, Vue, Next, etc.? 

CodePen isn't always ideal for these tools, so here are some Stackblitz starter templates that you can fork and import the gsap-trial NPM package for using any of the bonus plugins: 

 

Please share the StackBlitz link directly to the file in question (where you've put the GSAP code) so we don't need to hunt through all the files. 

 

Please don't include your whole project. Just some colored <div> elements and the GSAP code is best. See if you can recreate the issue with as few dependancies as possible. Start minimal and then incrementally add code bit by bit until it breaks. Usually people solve their own issues during this process! If not, at least we have a reduced test case which greatly increases your chances of getting a relevant answer.

 

Once we see an isolated demo, we'll do our best to jump in and help with your GSAP-specific questions. 

Link to comment
Share on other sites

Hi,

 

If this is working as a standalone component but it breaks in your app, that means that something else in your app is breaking this and is not a GSAP related issue.

 

You should remove other features of your app and start adding them until this breaks and you'll have the culprit of the situation. Sorty I can't be of more assistance but as mentioned this is more related to something else in your app rather than a GSAP issue.

 

Finally this looks odd to me:

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
  },
})

Why are you doing this?

loop.timeScale(Math.abs(self.deltaX) > Math.abs(self.deltaY) ? -self.deltaX : -self.deltaY) // whichever direction is bigger
slow.invalidate().restart() // now decelerate

Just create a timeline and be done with it:

let loop = horizontalLoop(`.carousel-${section} li`, { repeat: -1 })
let t;
loop?.timeScale(0)

Observer.create({
  target: `.carousel-${section}`,
  type: 'pointer,touch,wheel',
  wheelSpeed: -1,
  preventDefault: true,
  onChange: (self) => {
    t && t.kill();
    loop.timeScale(Math.abs(self.deltaX) > Math.abs(self.deltaY) ? -self.deltaX : -self.deltaY) // whichever direction is bigger
    t = gsap.to(loop, { timeScale: 0 });
  },
})

Creating a new GSAP instance won't have any negative effect performance wise and the previous one that is being killed will be sent to garbage collection and the small amount of memory it uses will be released so there is no downside to it.

 

https://stackblitz.com/edit/vitejs-vite-auctqy?file=src%2FApp.jsx

 

Happy Tweening!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...