Jump to content
Search Community

Scroll horizontally in a card

Sahn test
Moderator Tag

Recommended Posts

I'm trying to change content based on button clicks (they're not really buttons, it's just showing the window you're in). Within the second item that I added that code. I have an idea of how to it, but not sure if I'm going about it the right way, and also if there is a way to change the button color depending on the window you are in. Additionally, with the the navbar, would it be possible to make the each of the links open up at the specific card - so if you click about, it takes you to the about card and opens it for you? The thing is I'm using next.js and I have the card section code in a separate file from page.tsx and navbar.tsx file so I'm not sure how to go about that. I included a navbar.tsx and a page.tsx file in this demo here: https://stackblitz.com/edit/stackblitz-starters-u4ogx2?file=app%2Fpage.tsx 

Link to comment
Share on other sites

I can't figure out the logic you're using, but the best thing to do when working with ScrollTrigger is to remove it! This seems counter intuitive, but ScrollTrigger is just animating something on scroll, so just focus on the animation at first and only when you're happy with the animation add ScrollTrigger back in. This way you can focus on one part at a time and it will save a lot of headache when debugging. 

 

So the reiterate, just build the animation you want to happen on a timeline, then when you're happy with that add ScrollTrigger.

 

For the scrolling part you can use the scrollTo plugin and add labels to the timeline at spots you want to scroll to. You can give them logical names like "About", "Project", ect or label${i} like I did and then add that to the clickable element data-label="label0" and then with the scrollTo plugin you can do something like this   gsap.to(window, { scrollTo: tl.scrollTrigger.labelToScroll(e.target.dataset.label) });

 

Hope it helps and happy tweening! 

 

https://gsap.com/docs/v3/Plugins/ScrollTrigger/labelToScroll()/

 

https://stackblitz.com/edit/stackblitz-starters-oy3ouw?file=app%2Fpage.tsx

  • Like 1
Link to comment
Share on other sites

I think I'm closer now with the logic part, I put a scrollTrigger within another scrollTrigger so that when you hit the second card - Projects which has the container className, it should scroll horizontally to show all the sections. For some reason, the trigger is not registering when I hit the container div and so the st timeline doesn't run at all. Also, how could I link the window slider to show what slide your on based on the section you've scrolled into. 

https://stackblitz.com/edit/stackblitz-starters-u4ogx2?file=app%2Fpage.tsx 

Link to comment
Share on other sites

Hi,

 

I think the issue here stems from the approach you're using. Why not use a single GSAP Timeline to control all your accordion and then tie that to a ScrollTrigger?

 

I don't have time to make everything for you but this is the timeline at least:

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

 

Hopefully this helps.

Happy Tweening!

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

17 hours ago, Rodrigo said:

Hi,

 

I think the issue here stems from the approach you're using. Why not use a single GSAP Timeline to control all your accordion and then tie that to a ScrollTrigger?

 

I don't have time to make everything for you but this is the timeline at least:

 

 

 

Hopefully this helps.

Happy Tweening!

The reason I did two scrollTriggers is because I want the div that holds all the sliding info for the second slide to be triggered once the user scrolls and hits that div. I tried your method but ended up adding a scrollTrigger again because it wasn't pinning it and scrolling horizontally. I also tried changing

 const mainSlide = slideItem.querySelector(".main-slide") as HTMLElement | null;
  const mainSlide = slideItem.querySelector(".container") as HTMLElement | null;

 because I thought the div I chose was the issue but that didn't seem to work either. 
https://stackblitz.com/edit/stackblitz-starters-b9icb8?file=app%2Fpage.tsx 

Link to comment
Share on other sites

Hi,

 

You're making one of the most common ScrollTrigger mistakes, which is nesting a ScrollTrigger on a child instance inside a Timeline:

https://gsap.com/resources/st-mistakes#nesting-scrolltriggers-inside-multiple-timeline-tweens

 

Regardless of the approach you use, what you're doing right now is bound to create logical issues as explained.

 

Happy Tweening!

Link to comment
Share on other sites

1 hour ago, Rodrigo said:

Hi,

 

You're making one of the most common ScrollTrigger mistakes, which is nesting a ScrollTrigger on a child instance inside a Timeline:

https://gsap.com/resources/st-mistakes#nesting-scrolltriggers-inside-multiple-timeline-tweens

 

Regardless of the approach you use, what you're doing right now is bound to create logical issues as explained.

 

Happy Tweening!

I understand that using the ScrollTrigger can cause issues if used that way however, you can only pin in a ScrollTrigger so if I used a single GSAP timeline then it can only be pinned once in the original ScrollTrigger right? What about if I wanted to pin and scrub within the second card - Projects? 
https://stackblitz.com/edit/stackblitz-starters-b9icb8?file=app%2Fpage.tsx 

Link to comment
Share on other sites

I'm not 100% sure I understand what you're asking but for what I can infer you're trying to create an horizontal pin, so the cards that are moving horizontally pause at some point while scrubbing? If so, that can't be done, horizontal pinning is not supported when using vertical scrolling, you are scrolling in the Y axis but you want to pin on the X axis. ScrollTrigger simply can't manage that. The demo pins an element vertically, inside that element a bunch of panels are animating on the X axis but the scroll is still vertical (on the Y axis). In order to mimic horizontal pinning you have to pause that animation on the X axis, the one that moves the cards.

 

Here is the previous demo using ScrollTrigger:

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

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

1 hour ago, Rodrigo said:

I'm not 100% sure I understand what you're asking but for what I can infer you're trying to create an horizontal pin, so the cards that are moving horizontally pause at some point while scrubbing? If so, that can't be done, horizontal pinning is not supported when using vertical scrolling, you are scrolling in the Y axis but you want to pin on the X axis. ScrollTrigger simply can't manage that. The demo pins an element vertically, inside that element a bunch of panels are animating on the X axis but the scroll is still vertical (on the Y axis). In order to mimic horizontal pinning you have to pause that animation on the X axis, the one that moves the cards.

 

Here is the previous demo using ScrollTrigger:

 

 

 

Happy Tweening!

I see, lets say you had information that was previously in the second slide - it could be a paragraph of information and you wanted to change that paragraph to the horizontal sliding that you have currently when the user scrolls in that slide. Would you have to set the horizontal sliding animation to initially have an opacity: 0 and then when a user scrolls on the paragraph information, change that to an opacity: 0 to make it disappear and then show the horizontal sliding animation by doing opacity: auto?

Link to comment
Share on other sites

Sorry, I read your last post a few times but I'm afraid I don't understand what you mean. Please provide a minimal demo that clearly illustrates the problem. For example your last demo has this:

tlCards.to(slide, {
  x: () => -(slide.clientWidth - slide.clientWidth),
  ease: 'none',
  duration: 1,
});

Can you see the problem here? You have this slide.clientWidth - slide.clientWidth which equals to zero, so nothing happens.

 

I strongly suggest you to get something working like this first:

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

 

Just forget about ScrollTrigger and get the animation working as you want/need first, then add ScrollTrigger back to the mix. Probably including ScrollTrigger is adding an extra layer of work that you don't have to worry about right now. Focus only in the animation and then add ScrollTrigger, that is the easy part.

 

Happy Tweening!

  • Thanks 1
Link to comment
Share on other sites

3 hours ago, Rodrigo said:

Sorry, I read your last post a few times but I'm afraid I don't understand what you mean. Please provide a minimal demo that clearly illustrates the problem. For example your last demo has this:

tlCards.to(slide, {
  x: () => -(slide.clientWidth - slide.clientWidth),
  ease: 'none',
  duration: 1,
});

Can you see the problem here? You have this slide.clientWidth - slide.clientWidth which equals to zero, so nothing happens.

 

I strongly suggest you to get something working like this first:

 

 

 

Just forget about ScrollTrigger and get the animation working as you want/need first, then add ScrollTrigger back to the mix. Probably including ScrollTrigger is adding an extra layer of work that you don't have to worry about right now. Focus only in the animation and then add ScrollTrigger, that is the easy part.

 

Happy Tweening!

I overlooked the slide.clientWidth part, I fixed that now and I am trying to get the animation down without worry about the scrollTrigger following your approach. I just don't understand why nothing is happening to the second card to change its content to the slide and slideItems div when that card opens. 

https://stackblitz.com/edit/stackblitz-starters-b9icb8?file=app%2Fpage.tsx

I have tlCards.to(cardsContainer and tlCards.fromTo(content because I don't know which one will make the information from the slide div appear in that second card to replace the current text when they start scrolling within it. 

Link to comment
Share on other sites

Hi,

 

Unfortunately you didn't followed my advice since the animation is still tied up to ScrollTrigger:

const tlCards = gsap.timeline({
  scrollTrigger: {
    trigger: '.accordion-section1',
    start: 'top top',
    end: '+=1000',
    pin: true,
    scrub: true,
    invalidateOnRefresh: true,
    markers: true,
  },
});

I removed that, put the timeline on an endless loop and removed the ScrollTrigger part. This is what your timeline is currently doing:

https://stackblitz.com/edit/stackblitz-starters-4ftocx?file=app%2Fpage.tsx

 

That being said, unfortunately I still can't understand what you're trying to do. Once again I would strongly recommend you to even drop all the Next/React and other frameworks stuff and focus only on the animation, that's why I keep bringing back the codepen demos. Porting those to react is super easy, just focus on the animation.

 

Happy Tweening!

  • Thanks 1
Link to comment
Share on other sites

20 hours ago, Rodrigo said:

Hi,

 

Unfortunately you didn't followed my advice since the animation is still tied up to ScrollTrigger:

const tlCards = gsap.timeline({
  scrollTrigger: {
    trigger: '.accordion-section1',
    start: 'top top',
    end: '+=1000',
    pin: true,
    scrub: true,
    invalidateOnRefresh: true,
    markers: true,
  },
});

I removed that, put the timeline on an endless loop and removed the ScrollTrigger part. This is what your timeline is currently doing:

https://stackblitz.com/edit/stackblitz-starters-4ftocx?file=app%2Fpage.tsx

 

That being said, unfortunately I still can't understand what you're trying to do. Once again I would strongly recommend you to even drop all the Next/React and other frameworks stuff and focus only on the animation, that's why I keep bringing back the codepen demos. Porting those to react is super easy, just focus on the animation.

 

Happy Tweening!

You're right, I didn't take that part out and I haven't been explaining myself correctly. When iterating through elements using forEach, I want to target a specific element to do another animation within that loop. So I want to animate item when i == 1 from one set of properties to another but across two different elements (from .items to .slideItem) which I attempted to do using .fromTo over here: https://stackblitz.com/edit/stackblitz-starters-fw7d1f?file=app%2Fpage.tsx I am also using .add because it's supposed to show the transition from .items to .slideItem.

Link to comment
Share on other sites

Hi,

 

This is mostly a JS logic/selectors issue and not GSAP related. You have this:

tlCards.fromTo(
  '.slideItem',
  { opacity: 0 },
  { opacity: 0.5, duration: 1 }
);

You're running that code on iteration of the loop, so on each instance of the loop you're selecting every item with that class, you have to select the corresponding child of that particular accordion item.

 

Here is a fork of my previous demo that selects each <p> tag:

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

 

Hopefully this helps.

Happy Tweening!

  • Thanks 1
Link to comment
Share on other sites

On 3/14/2024 at 4:18 PM, Rodrigo said:

Hi,

 

This is mostly a JS logic/selectors issue and not GSAP related. You have this:

tlCards.fromTo(
  '.slideItem',
  { opacity: 0 },
  { opacity: 0.5, duration: 1 }
);

You're running that code on iteration of the loop, so on each instance of the loop you're selecting every item with that class, you have to select the corresponding child of that particular accordion item.

 

Here is a fork of my previous demo that selects each <p> tag:

 

 

 

Hopefully this helps.

Happy Tweening!

Thank you for pointing that out, the cards are moving again but it's still wonky. Is using a .fromTo appropriate or is .from and .to better? Again I'm trying to make the slideItem show after they see the initial text:

  <p>Check out the different transportation vehicles</p>

and scroll within the card to then iterate over each slideItem
https://stackblitz.com/edit/stackblitz-starters-fw7d1f?file=app%2Fpage.tsx 

Link to comment
Share on other sites

Sorry but I don't really understand what you mean with wonky, unfortunately vague descriptions like it's broken, doesn't work, wonky, etc. don't really tell us what the issue is 🤷‍♂️

 

Please be more specific about what is not working.

 

Finally this doesn't look good:

items.forEach((item1, i) => {
  let sections = gsap.utils.toArray('.object .image') as HTMLElement[];
  const slide = item1.querySelector('.slide') as HTMLElement | null;
  const content = item1.querySelector('.content1') as HTMLElement | null;
  const hasChild = item1.dataset.hasChild;
  tlCards
    .to(item1, { height: 0 })
    .to(items[i + 1], { height: 'auto' }, '<');

  document.querySelectorAll('.slideItem').forEach((slideItem) => {
    const childItem = slideItem.querySelector('.item');
    if (slide) {
      const hasChild = slide.dataset.hasChild;
      if (hasChild) {
        tlCards.fromTo(
          childItem,
          {
            x: slide.clientWidth - slide.clientHeight,
            opacity: 0,
            duration: 4,
          },
          { opacity: 1, duration: 1 }
        );
      }
    }
  });
});

Why are you nesting loops? Why are you doing a conditional check inside the second loop for the slide constant that is defined outside that loop? That all spells trouble to me.

 

Once again (this is not the first time I recommended this to you) drop all the complex stuff and stay away from react and other frameworks and focus on making the most elemental part of this work first, then port it to react and add more functionality to it.

 

Happy Tweening!

Link to comment
Share on other sites

On 3/20/2024 at 5:45 PM, Rodrigo said:

Sorry but I don't really understand what you mean with wonky, unfortunately vague descriptions like it's broken, doesn't work, wonky, etc. don't really tell us what the issue is 🤷‍♂️

 

Please be more specific about what is not working.

 

Finally this doesn't look good:

items.forEach((item1, i) => {
  let sections = gsap.utils.toArray('.object .image') as HTMLElement[];
  const slide = item1.querySelector('.slide') as HTMLElement | null;
  const content = item1.querySelector('.content1') as HTMLElement | null;
  const hasChild = item1.dataset.hasChild;
  tlCards
    .to(item1, { height: 0 })
    .to(items[i + 1], { height: 'auto' }, '<');

  document.querySelectorAll('.slideItem').forEach((slideItem) => {
    const childItem = slideItem.querySelector('.item');
    if (slide) {
      const hasChild = slide.dataset.hasChild;
      if (hasChild) {
        tlCards.fromTo(
          childItem,
          {
            x: slide.clientWidth - slide.clientHeight,
            opacity: 0,
            duration: 4,
          },
          { opacity: 1, duration: 1 }
        );
      }
    }
  });
});

Why are you nesting loops? Why are you doing a conditional check inside the second loop for the slide constant that is defined outside that loop? That all spells trouble to me.

 

Once again (this is not the first time I recommended this to you) drop all the complex stuff and stay away from react and other frameworks and focus on making the most elemental part of this work first, then port it to react and add more functionality to it.

 

Happy Tweening!

That code was really a mess, I did simplify it and got the horizontal scrolling of each slideItem to work in the Projects Card . Just one question, if I wanted to get rid of the "Check out the different transportation vehicles" text do I need another trigger for that or should I specifically target the second card in the items.forEach and somehow change that placeholder text when the user starts scrolling to then show each of the slideItems. 

https://stackblitz.com/edit/stackblitz-starters-b9icb8?file=app%2Fpage.tsx 

Link to comment
Share on other sites

  • 2 weeks later...

So I'm attempting to connect the href links (About, Projects, Contact) that I have in my navbar.tsx to page.tsx which has the gsap animation code -tlCards and this animates each card section to expand. So the navbar links are in another file and I'm also importing it in Layout.tsx file so it can show up throughout the whole application, the gsap code that animates tlCards is in a separate file (page.tsx).  Is it possible to call hrefs from another file and use gsap animation on it, I don't want to redefine those links in page.tsx again because that would be redundant and unnecessary. I was going to try using addEventListeners but that would work if I have the links in the page.tsx file which I don't. Maybe useRef could work here but I'm not too sure about that. I do have a lot of logic in my code and that's because it listens for a custom event - 'navigateToSection' and uses GSAP to scroll to the page section specified by the event's targetId. I'm also not sure if target.offsetTop is what I should be doing or tlCards.offsetTop. 

https://stackblitz.com/edit/stackblitz-starters-ne3hdu?file=app%2Fpage.tsx

Link to comment
Share on other sites

Hi,

 

This is no case a GSAP related problem. The issues you have start with the fact that you are using regular <a> tags with an href in them, as you can see as soon as you click on one of those the navbar is no longer there. Also I have no idea what are you doing here TBH:
 

const handleNavLinkClick = (
  event: React.MouseEvent<HTMLAnchorElement>,
  targetId: string
) => {
  event.preventDefault();
  window.dispatchEvent(
    new CustomEvent('navigateToSection', { detail: { targetId } })
  );
};

Also that click handler is not added to your <a> tags so nothing really happens. The approach of preventing the default behaviour is the correct though so you should keep that. This is about moving the scroll position to a specific point so it lands at the start of a specific section, like in this demo:

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

 

Then you have the problem about communicating between components, that is not always a simple thing in React and in some cases in most of this type of frameworks, so this becomes mostly a state management problem, where the target link is updated and depending on that, some code runs and your code can use that in order to automatically scroll to a specific point. Unfortunately, as I mentioned before, this is a state management and react-specific problem that is beyond the scope of what we do in these free forums. We don't have the time resources to create a solution for a react-specific problem for you, we need to keep our focus on GSAP related issues.

 

If I was you I'd look into react context for this, since is the simplest state management solution out there to make this work:

https://react.dev/learn/passing-data-deeply-with-context

 

Another option could be a custom hook, but that definitely is a more complex setup:

https://react.dev/learn/reusing-logic-with-custom-hooks

 

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

21 hours ago, Rodrigo said:

Hi,

 

This is no case a GSAP related problem. The issues you have start with the fact that you are using regular <a> tags with an href in them, as you can see as soon as you click on one of those the navbar is no longer there. Also I have no idea what are you doing here TBH:
 

const handleNavLinkClick = (
  event: React.MouseEvent<HTMLAnchorElement>,
  targetId: string
) => {
  event.preventDefault();
  window.dispatchEvent(
    new CustomEvent('navigateToSection', { detail: { targetId } })
  );
};

Also that click handler is not added to your <a> tags so nothing really happens. The approach of preventing the default behaviour is the correct though so you should keep that. This is about moving the scroll position to a specific point so it lands at the start of a specific section, like in this demo:

 

 

 

Then you have the problem about communicating between components, that is not always a simple thing in React and in some cases in most of this type of frameworks, so this becomes mostly a state management problem, where the target link is updated and depending on that, some code runs and your code can use that in order to automatically scroll to a specific point. Unfortunately, as I mentioned before, this is a state management and react-specific problem that is beyond the scope of what we do in these free forums. We don't have the time resources to create a solution for a react-specific problem for you, we need to keep our focus on GSAP related issues.

 

If I was you I'd look into react context for this, since is the simplest state management solution out there to make this work:

https://react.dev/learn/passing-data-deeply-with-context

 

Another option could be a custom hook, but that definitely is a more complex setup:

https://react.dev/learn/reusing-logic-with-custom-hooks

 

Hopefully this helps.

Happy Tweening!

Thank you for clarifying what the main issue would be for the navbar. Going back to the sliding card, I noticed the second & third section card doesn't slide out and expand like the first card does, instead it abruptly opens and closes here: https://stackblitz.com/edit/stackblitz-starters-ne3hdu?file=app%2Fpage.tsx. Is there a better approach to make the slideItems.forEach slower rather than increasing the duration? Also I added position absolute to the slideItems and a position relative to the main-slide div because I wanted the objects to show up in the same line instead of different lines. However, the card isn't expanding to show it or gives it extra space if the text is too long.

Link to comment
Share on other sites

1 hour ago, Sahn said:

Also I added position absolute to the slideItems and a position relative to the main-slide div because I wanted the objects to show up in the same line instead of different lines. However, the card isn't expanding to show it or gives it extra space if the text is too long.

Just use display flex as in the last demo I shared:

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

 

1 hour ago, Sahn said:

Is there a better approach to make the slideItems.forEach slower rather than increasing the duration?

Duration has nothing to in this case, when using ScrollTrigger to scrub a Tween/Timeline is about the amount of pixels the user scrolls:

 

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