Jump to content
GreenSock

Duo

Issues with horizontal scrolling and scrollTrigger animations

Go to solution Solved by GreenSock,

Recommended Posts

Hi,

 

Hoping that a genius in these forums can help me out.

 

I've got a project which is using horizontal scrolling, scrollTrigger and smoothScroller.

 

You can view it here: https://orconeau.com/cecilstreet/spaces/location/

 

The issue I'm having is that when the user scrolls towards the end of the horizontal scrolling section, it's adding a huge gap and not stopping on the last section with it centered. I've spent hours trying to tweak the math but I just can't get it to work properly and I'm pulling my hair out.

 

Happy to set up a codepen but I wanted to see if anyone could have a look at the STAGING link and the code that I'm using and maybe make a suggestion.

 

The CSS is:

[data-component="horizontal-scroller"] {
  height: 100vh;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  box-sizing: border-box;
  .cards {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    width: 100%;
    height: 100%;
    flex-shrink: 0;
    gap: 10vw;
    .card {
      width: auto;
      height: 100%;
      display: flex;
      align-items: center;
      flex-shrink: 0;
      justify-content: center;
      background-color: blue;
      backface-visibility: hidden;
      overflow: visible;
      padding-top: 101px;
      padding-bottom: 120px;
      box-sizing: border-box;
      .inner {
        height: 100%;
        position: relative;
        will-change: transform;
        transform-style: preserve-3d;
      }
      picture {
        img {
          @include app-breakpoint-3 {
            width: auto;
            height: 100%;
          }
        }
      }
      &:nth-of-type(odd) {
        background-color: purple;
      }
    }
  }
}

 

The javascript is:

const horizontalScroller = document.querySelector('[data-component="horizontal-scroller"]');
const horizontalCards = horizontalScroller.querySelector('.cards')
const horizontalCard = horizontalCards.querySelectorAll('.card')
    
gsap.set(horizontalCards, {
  marginLeft: '50vw'
})

gsap.set(horizontalCard, {
  perspective: 750
})

const smoother = ScrollSmoother.create({
  smooth: 1.5,
  effects: true,
  smoothTouch: false
})

const horizontalTween = gsap.to(horizontalCards, {
  x: () => { return -((horizontalCards.scrollWidth - window.innerWidth * 0.5) + (window.innerWidth / 2 - horizontalCard[horizontalCard.length - 1].offsetWidth / 2)) },
  ease: "none",
  scrollTrigger: {
    trigger: horizontalScroller,
    start: () => "top top",
    end: () => `+=${((horizontalCards.scrollWidth - window.innerWidth * 0.5) + (window.innerWidth / 2 - horizontalCard[horizontalCard.length - 1].offsetWidth / 2))}`,
    scrub: true,
    pin: true,
    markers: false,
    invalidateOnRefresh: true,
    anticipatePin: 1
  }
});

horizontalCard.forEach((card, i) => {
  const content = card.querySelector('.inner')
  gsap.set(content, {
    rotateY: -100,
    rotateX: 25,
    yPercent: -10,
    scale: 2.5,
    xPercent: 100
  })
  const tween = gsap.to(content, {
    rotateY: 0,
    rotateX: 0,
    yPercent: 0,
    xPercent: 0,
    scale: 1,
    force3D: true
  })
  ScrollTrigger.create({
    trigger: card,
    containerAnimation: horizontalTween,
    start: "left 75%",
    end: "50% 50%",
    scrub: 1,
    markers: true,
    animation: tween
  });
});

 

I've got the horizontalTween working and then I have an animation for each .card element inside the scrolling .cards section. The animation in there is doing some funky transforms and the issue is that the scrollWidth is including the transforms when it's determining the width of each card. I think.

 

When I turn it off, the scrolling overflow is better but I lose the effect.

 

Any help would be greatly appreciated.

 

Thanks

Link to comment
Share on other sites

  • Duo changed the title to Issues with horizontal scrolling and scrollTrigger animations

I have gone ahead and created a CodePen for testing:

 

See the Pen RwygjWb by dayneh88 (@dayneh88) on CodePen

 

Really hoping that someone can provide assistance.

 

Thanks

Link to comment
Share on other sites

I’m desperate for some help on this haha. Hoping that some one out there can heed the call. 

Link to comment
Share on other sites

Hi @Duo,

 

Sorry for the late response, this fell through the cracks.

 

Right now there's a blackout here so I'm looking at this in my phone, which is not the best option. If I find something that can be helpful I'll post back.

 

AS soon as the power is back I'll dive into this so we can sort it out ASAP

 

Please a bit more patience 🙏

Link to comment
Share on other sites

Hi @Rodrigo

 

Thanks so much for getting back to me. Huge respect for you looking at this on your phone, in the middle of a blackout.

 

I really appreciate you offering to help me out.

 

Thank you again

  • Like 1
Link to comment
Share on other sites

Yeah, those transforms are contaminating the measurements you're doing in your "x" function-based property. So you can just use a gsap.set() to put them back to "normal", do your measurement, then revert() that set() call to return things to the way they were. Like this: 

 

See the Pen WNJOVmP?editors=0010 by GreenSock (@GreenSock) on CodePen

 

Is that more like what you wanted? 

  • Like 2
Link to comment
Share on other sites

HI,

 

Thanks for getting back to me and helping out. Greatly appreciated.

 

I was getting inconsistencies across browsers and sometimes it wasn't even initialising the scrolling functionality (Safari) but it seems to be working much better when I run the functionality for the horizontal scrolling inside a window.addEventListener('load', (event)

 

Is it ok to have this functionality initiate in the window load event?

 

This is the code inside my app.js:

 

import '../sass/app.scss';
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger'
import { ScrollSmoother } from 'gsap/ScrollSmoother'
import { ScrollToPlugin } from 'gsap/ScrollToPlugin'
gsap.registerPlugin(ScrollTrigger, ScrollSmoother, ScrollToPlugin)

class CecilStreet {
  constructor() {
    this.initCecilStreet();
  }

  initCecilStreet() {

    ScrollSmoother.create({
      smooth: 1,               // how long (in seconds) it takes to "catch up" to the native scroll position
      effects: true,           // looks for data-speed and data-lag attributes on elements
      smoothTouch: 0.1,        // much shorter smoothing time on touch devices (default is NO smoothing on touch devices)
    });

    window.addEventListener('load', (event) => {
      const horizontalScroller = document.querySelector('[data-component="horizontal-scroller"]');
      const horizontalCards = horizontalScroller.querySelector('.cards')
      const horizontalCard = horizontalCards.querySelectorAll('.card')

      gsap.set(horizontalCards, {
        marginLeft: '50vw'
      })

      gsap.set(horizontalCard, {
        perspective: 750
      })

      const horizontalTween = gsap.to(horizontalCards, {
        x: () => {
          let cleaner = gsap.set(".card .inner", {rotateY: 0, rotateX: 0, yPercent: 0, xPercent: 0, scale: 1}),
              width = -(horizontalCards.scrollWidth - window.innerWidth * 0.5 + (window.innerWidth / 2 - horizontalCard[horizontalCard.length - 1].offsetWidth / 2));
          cleaner.revert();
          return width;
        },
        ease: "none",
        scrollTrigger: {
          trigger: horizontalScroller,
          start: () => "top top",
          end: () =>
            `+=${
              horizontalCards.scrollWidth - window.innerWidth * 0.5 + (window.innerWidth / 2 - horizontalCard[horizontalCard.length - 1].offsetWidth / 2)
            }`,
          scrub: true,
          pin: true,
          markers: false,
          invalidateOnRefresh: true,
          anticipatePin: 1
        }
      });

      horizontalCard.forEach((card, i) => {
        const content = card.querySelector(".inner");
        gsap.set(content, {
          rotateY: -100,
          rotateX: 25,
          yPercent: -10,
          scale: 2.5,
          xPercent: 100
        });
        const tween = gsap.to(content, {
          rotateY: 0,
          rotateX: 0,
          yPercent: 0,
          xPercent: 0,
          scale: 1,
          force3D: true
        });
        ScrollTrigger.create({
          trigger: card,
          containerAnimation: horizontalTween,
          start: "left 75%",
          end: "50% 50%",
          scrub: 1,
          animation: tween
        });
      });
    })

  }
}

export default CecilStreet;

const cecilStreet = new CecilStreet()

 

I'm so close to having this working the way I want it to across browsers so I really do appreciate any continued support in order to nail this.

 

Thanks

Link to comment
Share on other sites

Also, when the browser is resized the width isn't recalculated so the gap at the end appears. On Firefox scrolling almost fails to work again.

 

I think on resize the x: => width needs to be recalculated.

Link to comment
Share on other sites

Safari has a known major bug (the browser, not a problem in GSAP) and we've worked around it in the next release of GSAP - would you mind testing with the latest beta files?:

If you're still having trouble, would you please provide a minimal demo and a clear set of steps to reproduce the problem? I'd be happy to take a peek. 

Link to comment
Share on other sites

Hi Jack,

 

What is the issue that you've addressed on Safari in the latest beta?  asI've got it working in Safari?

 

I've managed to get it all working across browsers successfully.

 

I have placed all of the functionality inside a window.load event with smoothScroller initialising before the window.load event.

 

I also added ScrollTrigger.normalizeScroll(true); after everything to stop the bouncing on the viewport in Safari when over-scrolling the start and end points of the page.

 

The only issue I'm having before I can officially sign this off is that when the browser is resized, the scroll distance seems to not be updated. This is very evident in Firefox. If you view the STAGING link you'll see what I mean: https://orconeau.com/cecilstreet/spaces/location/ (load and resize the browser)

 

This is what I have in my app.js:

 

import '../sass/app.scss';
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger'
import { ScrollSmoother } from 'gsap/ScrollSmoother'
import { ScrollToPlugin } from 'gsap/ScrollToPlugin'
gsap.registerPlugin(ScrollTrigger, ScrollSmoother, ScrollToPlugin)

class CecilStreet {
  constructor() {
    this.initCecilStreet();
  }

  initCecilStreet() {

    ScrollSmoother.create({
      smooth: 2,
      effects: false,
      smoothTouch: 0.1
    })

    window.addEventListener('load', (event) => {

      document.querySelectorAll('[data-component="horizontal-scroller"]').forEach(element => {
        const horizontalCards = element.querySelector('.cards')
        const horizontalCard = element.querySelectorAll('.card')

        gsap.set(horizontalCards, {
          marginLeft: '50vw'
        })

        gsap.set(horizontalCard, {
          perspective: 750
        })

        const horizontalTween = gsap.to(horizontalCards, {
          x: () => {
            let cleaner = gsap.set(".card .inner", { rotateY: 0, rotateX: 0, yPercent: 0, xPercent: 0, scale: 1 }),
              width = -(horizontalCards.scrollWidth - window.innerWidth * 0.5 + (window.innerWidth / 2 - horizontalCard[horizontalCard.length - 1].offsetWidth / 2));
            cleaner.revert();
            return width;
          },
          ease: "none",
          scrollTrigger: {
            trigger: element,
            start: () => "top top",
            end: () =>
              `+=${horizontalCards.scrollWidth - window.innerWidth * 0.5 + (window.innerWidth / 2 - horizontalCard[horizontalCard.length - 1].offsetWidth / 2)
              }`,
            scrub: true,
            pin: true,
            markers: false,
            invalidateOnRefresh: true,
            anticipatePin: 1
          }
        });

        horizontalCard.forEach((card, i) => {
          const content = card.querySelector(".inner");
          gsap.set(content, {
            rotateY: -100,
            rotateX: 25,
            yPercent: -10,
            scale: 2.5,
            xPercent: 100
          });
          const tween = gsap.to(content, {
            rotateY: 0,
            rotateX: 0,
            yPercent: 0,
            xPercent: 0,
            scale: 1,
            force3D: true
          });
          ScrollTrigger.create({
            trigger: card,
            containerAnimation: horizontalTween,
            start: "left 75%",
            end: "50% 50%",
            scrub: 2,
            animation: tween
          });
        });

      })

    })

    ScrollTrigger.normalizeScroll(true);

  }

}

export default CecilStreet;

const cecilStreet = new CecilStreet()

 

Link to comment
Share on other sites

10 minutes ago, Duo said:

What is the issue that you've addressed on Safari in the latest beta?  asI've got it working in Safari?

Explained here, with a minimal demo showing it's unrelated to GSAP: 

 

10 minutes ago, Duo said:

The only issue I'm having before I can officially sign this off is that when the browser is resized, the scroll distance seems to not be updated. This is very evident in Firefox. If you view the STAGING link you'll see what I mean: https://orconeau.com/cecilstreet/spaces/location/ (load and resize the browser)

It's not feasible to troubleshoot live web sites because there are so many other factors involved and we can't tweak the code to see what's going on. Can you please provide a minimal demo (like in CodePen) that clearly shows the issue? That'll significantly improve your chances of getting a solid answer. 👍

 

I'd also be very curious to hear if using the beta files resolves things for you. 

Link to comment
Share on other sites

Hi @GreenSock

 

Thanks for your new CodePen. Appreciate you again, taking the time to assist me.

 

I've implemented your code exactly as you have it into my project and it works but only sometimes. I'm so over trying to get this to work. Why does nothing ever work in a real environment haha.

 

You can view the live page here: https://orconeau.com/cecilstreet/spaces/living/

 

If you open the page and have the viewport open half of the height of your screen and then resize it down so that it is full viewport height, the calculation is all off again. If you resize it again, some of the last few sections are cut off. Refresh the page completely and it calculates properly, but resize it different ways and it breaks. 

 

Your demo though, works perfectly. Across all browsers.

 

I even tried turning off scrollSmoother and changing the eventListener to "refresh", instead of "refreshInit" on ScrollTrigger but the issue remains.

 

This is the code I've implemented:

 

import '../sass/app.scss';
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger'
import { ScrollSmoother } from 'gsap/ScrollSmoother'
import { ScrollToPlugin } from 'gsap/ScrollToPlugin'
gsap.registerPlugin(ScrollTrigger, ScrollSmoother, ScrollToPlugin)

class CecilStreet {
  constructor() {
    this.initCecilStreet();
  }

  initCecilStreet() {

    ScrollSmoother.create({
      smooth: 2,
      effects: false,
      smoothTouch: 0.1
    })

    window.addEventListener('load', (event) => {

      document.querySelectorAll('[data-component="horizontal-scroller"]').forEach(element => {
        const horizontalCards = element.querySelector('.cards')
        const horizontalCard = element.querySelectorAll('.card')

        gsap.set(horizontalCards, {
          marginLeft: '50vw'
        })

        gsap.set(horizontalCard, {
          perspective: 750
        })

        let distance

        function refreshDistance() {
          let cleaner = gsap.set(".card .inner", {
            rotateY: 0,
            rotateX: 0,
            yPercent: 0,
            xPercent: 0,
            scale: 1
          });
          distance =
            horizontalCards.scrollWidth -
            window.innerWidth * 0.5 +
            (window.innerWidth / 2 -
              horizontalCard[horizontalCard.length - 1].offsetWidth / 2);
          cleaner.revert();
        }

        refreshDistance();
        ScrollTrigger.addEventListener("refreshInit", refreshDistance);

        const horizontalTween = gsap.to(horizontalCards, {
          x: () => -distance,
          ease: "none",
          scrollTrigger: {
            trigger: element,
            start: () => "top top",
            end: () => "+=" + distance,
            scrub: true,
            pin: true,
            markers: false,
            invalidateOnRefresh: true,
            anticipatePin: 1
          }
        });

        horizontalCard.forEach((card, i) => {
          const content = card.querySelector(".inner");
          gsap.set(content, {
            rotateY: -100,
            rotateX: 25,
            yPercent: -10,
            scale: 2.5,
            xPercent: 100
          });
          const tween = gsap.to(content, {
            rotateY: 0,
            rotateX: 0,
            yPercent: 0,
            xPercent: 0,
            scale: 1,
            force3D: true
          });
          ScrollTrigger.create({
            trigger: card,
            containerAnimation: horizontalTween,
            start: "left 75%",
            end: "50% 50%",
            scrub: 2,
            animation: tween
          });
        });

      })

    })

    ScrollTrigger.normalizeScroll(true);

  }

}

export default CecilStreet;

const cecilStreet = new CecilStreet()

 

Link to comment
Share on other sites

Yeah, it's pretty tough to troubleshoot a live site like that, but there must be something else going on in your project that's messing with the calculations. I see you're doing a .forEach() for that whole block of code - do you have a bunch of sections? Perhaps the key is making sure that you remove the transforms from them all before doing your calculations (like if the others are "propping open" the container and throwing off your measurements? I'm totally guessing here - I don't have time to attempt troubleshooting a live site. As you mentioned, the code I provided seems to work consistently in all browsers so there's gotta be some other factor at play in your live site. 

 

If you need more help on a paid consulting basis, feel free to post in the Jobs & Freelance forum or contact us directly. 

 

Good luck! You can do this! 👍

  • Thanks 1
Link to comment
Share on other sites

Hi @GreenSock,

 

Apologies for not getting back to you sooner but thank you so much for all of your help and assistance.

 

There seems to be other issues going as to why it's throwing inconsistencies on resize but the client doesn't care haha so we are going to launch it with that issue there. Not ideal I know but they want the project live instead of investing more time into trying to fix it and holding up delivery.

 

Thank you again for all of your help and assistance, it is greatly appreciated.

Link to comment
Share on other sites

As for the inconsistencies on resize, would you mind trying the latest beta and letting me know if that resolves things? 

(You probably need to clear your cache)

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