Jump to content
Search Community

Inherit values from previous tween in scrollTrigger timeline

codedazur test
Moderator Tag

Recommended Posts

I'm trying to animate a SVG using a GSAP timeline and the ScrollTrigger plugin.

I want the tweens in my timeline to use the end values of the previous tween. However, every ScrollTrigger instance uses the initial values of the SVG as the starting point for the animation. When you enter the section-two div the radius of the circle SVG element resets to 200 instead of staying 50 (the end value of the previous tween in the timeline). See my Codepen for a simple example.

Please let me know if you need any more information and thanks in advance.

See the Pen xxZaBmR by mrworldpeace (@mrworldpeace) on CodePen

Link to comment
Share on other sites

Hey codedazur and welcome to the GreenSock forums! Thanks for supporting GreenSock by being a Business Green member.

 

The main issue is that ScrollTriggers have to be created when the page is initialized. So although you put the tween in a timeline which would normally sequence the tweens and animate like you're wanting, since you have a ScrollTrigger applied this does't happen. This also explains one reason why it doesn't make much sense to have ScrollTriggers applied to tweens in timelines.

 

I think the cleanest way for this to happen would be to create two independent tweens and ScrollTriggers and use a .fromTo for the second tween:

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

Link to comment
Share on other sites

Thanks for your response!

I understand what you're saying but this does mean that for complex animations (which is what I'm doing) I need to duplicate basically every tween (since they need to change from to to fromTo).

 

Also, currently I'm clearing and re-initialising my timeline on every window resize since my animations need to be 'responsive'. Everything becomes way more complex if I need to use separate tweens for every ScrollTrigger instead of a timeline. Oh well, at least it works...

Link to comment
Share on other sites

You could use Object.assign() to prevent you from having to duplicate the properties but it requires a little bit of markup:

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

 

Do you have any alternative suggestions for how this could work?

If you didn't need scrub then you could just use callbacks but with scrub I think this sort of approach is the only way to do it. But if you (or others) have ideas for how to better allow this sort of thing to happen we're all ears.

Link to comment
Share on other sites

By default, ScrollTrigger will force an initial render immediately so that the values get locked in right away (for better performance during scroll - basically doing the work up front). You can set immediateRender: false on your tween to avoid that if you'd like. 

  • Like 1
Link to comment
Share on other sites

My full code if someone every gets here and wonders on how I've solved it:

 

if (tl === undefined) {
  tl = gsap.timeline({
    defaults: {
      immediateRender: false,
    },
  });
} else {
  tl.clear();
}

tl.set(circleOneRef.current, {
  attr: {
    cx: getCircleCx(shapes.one.left),
    cy: getCircleCy(shapes.one.top),
    fill: shapes.one.fill,
    r: getCircleR(shapes.one.width),
  },
  scrollTrigger: {
    end: 'bottom top',
    markers: true,
    scroller: backgroundShapesRef.current,
    start: 'top top',
    trigger: sectionOneRef.current,
  },
})
  .to(circleOneRef.current, {
    attr: {
      cx: getCircleCx(shapes.two.left),
      cy: getCircleCy(shapes.two.top),
      fill: shapes.two.fill,
      r: getCircleR(shapes.two.width),
    },
    duration: 1,
    ease: 'elastic.out(1, 0.5)',
    immediateRender: true,
    scrollTrigger: {
      end: 'bottom top',
      markers: true,
      scroller: backgroundShapesRef.current,
      scrub: true,
      start: '20% top',
      trigger: sectionOneRef.current,
    },
  })
  .to(circleOneRef.current, {
    attr: {
      fill: shapes.three.fill,
      r: getFullscreenCircleR(),
    },
    duration: 1,
    ease: 'elastic.in(1, 0.2)',
    scrollTrigger: {
      end: 'bottom top',
      markers: true,
      scroller: backgroundShapesRef.current,
      scrub: true,
      start: 'top top',
      trigger: sectionTwoRef.current,
    },
  })
  .to(circleTwoRef.current, {
    attr: {
      cx: getCircleCx(shapes.five.left),
      cy: getCircleCy(shapes.five.top),
      fill: shapes.five.fill,
      r: getCircleR(shapes.five.width),
    },
    duration: 1,
    ease: 'elastic.out(1, 0.5)',
    immediateRender: true,
    scrollTrigger: {
      end: '80% top',
      markers: true,
      scroller: backgroundShapesRef.current,
      scrub: true,
      start: '20% top',
      trigger: sectionThreeRef.current,
    },
  })
  .fromTo(
    circleTwoRef.current,
    {
      attr: {
        cx: getCircleCx(shapes.five.left),
        cy: getCircleCy(shapes.five.top),
        fill: shapes.five.fill,
        r: getCircleR(shapes.five.width),
      },
    },
    {
      attr: {
        r: getFullscreenCircleR(),
      },
      duration: 1,
      ease: 'elastic.in(1, 0.2)',
      scrollTrigger: {
        end: 'bottom top',
        markers: true,
        scroller: backgroundShapesRef.current,
        scrub: true,
        start: '20% top',
        trigger: sectionFourRef.current,
      },
    },
  );

This code is being ran in an useEffect hook which runs every time the window size changes.

@GreenSock does this make sense or is it a bad solution?

Link to comment
Share on other sites

2 hours ago, codedazur said:

This code is being ran in an useEffect hook which runs every time the window size changes.

@GreenSock does this make sense or is it a bad solution?

In general you should avoid doing that sort of thing. It's best to create things once upon page initialization and then let ScrollTrigger/the browser handle resizing if possible.

  • Like 1
Link to comment
Share on other sites

That's not possible in my case since the animation values need to change according to the screen size. That's why I'm using functions like getCircleCx instead of hardcoded values (the getCircleCx function will return a different value according to the screen size).

Link to comment
Share on other sites

9 hours ago, codedazur said:

@GreenSock thanks for the solution, that indeed fixed it!

One question: by default immediateRender is false according to the docs for tween. Does this change to true by default when using a ScrollTrigger on said Tween?

Good catch - I updated the docs accordingly. Yes, ScrollTrigger forces an immediateRender unless you set immediateRender: false. 

  • Like 1
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...