codedazur Posted July 15, 2020 Share Posted July 15, 2020 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 More sharing options...
ZachSaucier Posted July 15, 2020 Share Posted July 15, 2020 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 More sharing options...
codedazur Posted July 15, 2020 Author Share Posted July 15, 2020 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 More sharing options...
ZachSaucier Posted July 15, 2020 Share Posted July 15, 2020 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 More sharing options...
ZachSaucier Posted July 15, 2020 Share Posted July 15, 2020 Another alternative is to use a ScrollTrigger with scrub on a timeline including both tweens, but I assumed you knew that already: See the Pen bGExyBB by GreenSock (@GreenSock) on CodePen Link to comment Share on other sites More sharing options...
GreenSock Posted July 15, 2020 Share Posted July 15, 2020 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. 1 Link to comment Share on other sites More sharing options...
codedazur Posted July 16, 2020 Author Share Posted July 16, 2020 @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? Link to comment Share on other sites More sharing options...
codedazur Posted July 16, 2020 Author Share Posted July 16, 2020 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 More sharing options...
ZachSaucier Posted July 16, 2020 Share Posted July 16, 2020 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. 1 Link to comment Share on other sites More sharing options...
codedazur Posted July 16, 2020 Author Share Posted July 16, 2020 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 More sharing options...
GreenSock Posted July 16, 2020 Share Posted July 16, 2020 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. 1 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now