Jump to content
Search Community

Infinite carousel loop with draggable

AntoninB test
Moderator Tag

Recommended Posts

I'm new to GSAP and I'm currently trying to create an image carousel (a marquee) which is looping infinitely and that would be draggable.

I managed to get the infinite loop part working thanks to this thread: 

And especially this pen (edit: I don't why but it's displaying at the end of this post): 

 

I adapted it and in the end I have an infinite carousel looping through 9 pictures and displaying 4 of them on the screen (each takes 25% of the screen).

 

But now I'm struggling with the draggable part. What I'm trying to do is adding the possibility to drag left/right inside the carousel like in this pen

See the Pen 1e13ae4d1583c9a7157b46b995345872 by osublake (@osublake) on CodePen

 

I'm using Next.js with Typescript and styled-components on this component.

 

I documented myself on how to use gsap with React and SSR the proper way, but I must admit I'm not getting all of it.

Right now, I juste copy/pasted the code from the above pen for the drag, and it's giving me this error as soon as I start dragging:

 

TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.

I think it is thrown by this line:

animation.progress(wrapProgress(props("x") / (wrapWidth)))

Espacially the props("x") part.

 

Any help would be appreciated, and thank you to the people of this forum who already taught me a lot.

 

Here is a copy/paste from my code, sorry it's a bit a mess, everything is in the useEffect cause I had to get access to the window object to determine my item size base on sceen size:

 

import React, { useEffect, useRef, useState } from "react"
import styled from "styled-components"
import images from "../../core/application/constants/carousel-pictures/places-bottom-carousel"
import gsap, { Linear } from "gsap"
import Draggable from "gsap/dist/Draggable"
import { breakpoints } from "../../themes/breakpoints"

const GsapCarousel: React.FC = () => {

  const container = useRef<HTMLDivElement>()
  const NUMBER_OF_CARDS = images.length

  const setCardsPosition = (): void => {
    gsap.set(".box", {
      x: (i) => i * (window.innerWidth / 4)
    });
  }

  useEffect(() => {
    gsap.registerPlugin(Draggable)
    let q = gsap.utils.selector(container)
    const proxy = q(".proxy")
    console.log('PROXY', proxy);
    console.log(q(".box"));
    
    const cardWidth = window.innerWidth / 4
    const snapBox = gsap.utils.snap(cardWidth)
    const wrapWidth = NUMBER_OF_CARDS * cardWidth
    const wrapProgress = gsap.utils.wrap(0, 1)
    const props = gsap.getProperty(proxy)

    setCardsPosition()
    gsap.to(q(".box"), {
      duration: 60,
      ease: "none",
      x: `+=${(window.innerWidth / 4) * NUMBER_OF_CARDS}`,
      modifiers: {
        x: gsap.utils.unitize(x => parseFloat(x) % ((window.innerWidth / 4) * NUMBER_OF_CARDS)) //force x value to be between 0 and 500 using modulus
      },
      repeat: -1
    });

    const animation = gsap.to(q(".box"), {
      duration: 1,
      x: "+=" + wrapWidth,
      ease: Linear.easeNone,
      paused: true,
      repeat: -1,
      modifiers: {
        x: function(x, target) {
          x = parseFloat(x) % wrapWidth;
          return x + "px";
        }
      }
    }).progress(1 / NUMBER_OF_CARDS);

    const updateProgress = () => {
      animation.progress(wrapProgress(props("x") / (wrapWidth)))
    }

    Draggable.create(proxy, {
      // type: "x", 
      trigger: q(".boxes"),
      throwProps: true,
      onDrag: updateProgress,
      onThrowUpdate: updateProgress,
      snap: {
        x: snapBox
      }
    });
  });

  const renderCard = (card, index) => {
    return (
      <CarouselItem key={index} className="box">
        <PlaceText>{card.text}</PlaceText>
        <Picture src={card.src}></Picture>
      </CarouselItem>
    )
  }

  return (
    <div ref={container as React.RefObject<HTMLDivElement>}>
      <div className="proxy"></div>
      <Wrapper>
        <CarouselContainer className="boxes">
          {images.map(renderCard)}
        </CarouselContainer>
      </Wrapper>
    </div>
  )
}

const Wrapper = styled.div`
  width: 100%;
  margin: auto;
  height: 400px;
  overflow: hidden;
  position: relative;
  margin-top: 200px;
  border: 2px solid red;
`

const CarouselContainer = styled.div`
  position: relative;

  @media ${breakpoints.laptop} {
    left: -25%;
  }
`

const CarouselItem = styled.div`
  width: 100%;
  position: absolute;
  border: 2px solid black;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  @media ${breakpoints.laptop} {
    width: 25%;
  }
`

const PlaceText = styled.span`
  color: ${({ theme }) => theme.text.color.red};
  font-family: ${({ theme }) => theme.text.family.atlanticCruise};
  display: inline-block;
  font-size: 3rem;
  margin-bottom: 2rem;
`

const Picture = styled.img`
  border-radius: 30px;
  width: 90%;
  user-select: none;
  user-drag: none;
`

export default GsapCarousel

 

See the Pen QEdpLe by GreenSock (@GreenSock) on CodePen

  • Thanks 1
Link to comment
Share on other sites

I don't have much time right now to dig into all that, @AntoninB but I'd strongly recommend looking at the Helper Functions page in the docs because there's one there that handles infinite looping on the x-axis and there's even a demo with Draggable hooked up: 

https://greensock.com/docs/v3/HelperFunctions#loop

 

That error you mentioned kinda sounds like maybe you either forgot to gsap.registerPlugin() before trying to use the plugin or perhaps you've got your scripts loading in the <head> instead of at the bottom of the <body>. I'm totally guessing since you didn't provide a minimal demo. If you still need some help, maybe you could provide a minimal demo in CodeSandbox? 

  • Like 1
Link to comment
Share on other sites

Hey @GreenSock,

 

Thank you so much for the reply. The helper function was indeed very handy, and I am now able to reproduce what I wanted to.

Sorry for I didn't show any demo (was struggling to get my React component working with Codepen), but here is now one with CodeSandbox:

 

As you see I was able to get the draggable part working thanks to the helper, but I can't get the infinite looping when the carousel is auto-playing. It only loops about one time and then stops. I tried to pass the repeat config param like so:

 

const loop = horizontalLoop(boxes, {
  paused: false,
  draggable: true,
  repeat: true
});

  But I don't feel like this config param is used anywhere in the helper. How would I make the carousel loops infinitely ?

  

Link to comment
Share on other sites

Hey @Cassie

 

Thank you for your help, repeat: -1  was indeed what I was looking for. I feel stupid not finding it myself...

I just have one last question. In the example I provided in the Codesandbox, the horizontalLoop() function is stored in a loop variable:

 

const loop = horizontalLoop(boxes, {
  paused: false,
  draggable: true,
  repeat: true
});

But that same loopvariable is used in the horizontalLoop function itself to handle the dragging part (in the onPress function for example).

 

So when I'm moving the horizontalLoop function to an external helper file (or outside the useEffect) like that:

https://codesandbox.io/s/sxe1n?file=/src/App.js

It's obviously throwing the loop is not defined error

 

I guess I'm missing something about how the loop is reused by the drag functions.

Link to comment
Share on other sites

Mmm.

I'm not really certain what's going on in your codesandbox (I'm not a react person)

But in the helper function Jack put together the horizontalLoop function is returning a GSAP timeline with some additional methods added to it, so it can be controlled with previous and next buttons.

Inside the function the progress of the timeline is being accessed so that the timeline can be dragged along from that point. You'll need to make sure you can access the timeline's progress within the function or it won't work.

I can't help you more than that though I'm afraid. 

Link to comment
Share on other sites

Thanks @Cassie for your guesses, you put me on the track saying I had to use the same timeline to handle my drag (which seems pretty obvious now). Basically I just kept using the same tl instance in the drag part, instead of loop, all in the same horizontalLoop function that I put outside my component to be able to reuse it. It's working this way. Note sure it's the best way to do it, I should be digging more into how Gsap and React work but I had to get this animation done quickly, and it's good now. Thank you !  

Link to comment
Share on other sites

  • 8 months later...

Hi,

 

@AntoninB I am a newbie and I don't really understood how you fixed your issue with the loop const error. I have the same use case, are you able to share how to fix it ? 
Does anyone would be able to help me ? My target is to fix the error on the loop variable which is not recognized because it is declared in the useEffect hook. 

https://codesandbox.io/s/sxe1n?file=/src/App.js

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...