-
Posts
5,037 -
Joined
-
Last visited
-
Days Won
246
Community Answers
-
Rodrigo's post in ScrollTrigger: Pinned Lateral Sections Expanded was marked as the answer
Hi,
Indeed @alig01 is right, is just about changing the DOM elements from images to anything you want/need.
In this fork I changed every image for an SVG element, each with a different shape in it:
See the Pen LYMbjQw by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Infinite Marquee was marked as the answer
Yeah, the reason is because the play method sets the reversed state to false, while when you create the instance the reversed state is true, so basically instead of play() use reverse() in your mouse event handler:
document .querySelector(".wrapper") .addEventListener("mouseenter", () => { loop.pause(); }); document .querySelector(".wrapper") .addEventListener("mouseleave", () => { loop.reverse(); }); Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in .from() in ScrollTrigger only triggers when scrolling up was marked as the answer
Hi @Léo Shcl and welcome to the GreenSock forums!
Thanks for being a Club GreenSock member and for supporting GreenSock! 💚
You are using React but you're not using GSAP Context. GSAP Context is your best friend when it comes to create animations with GSAP in React. This is especially important when using from() instances.
Perhaps the problem is that React 18 runs in "strict" mode locally by default which causes your useEffect() to get called TWICE! Very annoying. It has caused a lot of headaches for a lot of people outside the GSAP community too.
.from() tweens use the CURRENT value as the destination and it renders immediately the value you set in the tween, so when it's called the first time it'd work great but if you call it twice, it ends up animating from the from value (no animation). It's not a GSAP bug - it's a logic thing.
For example, let's say el.x is 0 and you do this:
useEffect(() => { // what happens if this gets called twice? gsap.from(el, {x: 100}) }, []);
The first time makes el.x jump immediately to 100 and start animating backwards toward the current value which is 0 (so 100 --> 0). But the second time, it would jump to 100 (same) and animate back to the current value which is now 100 (100 --> 100)! See the issue?
In GSAP 3.11, we introduced a new gsap.context() feature that solves all of this for you. All you need to do is wrap your code in a context call, and then return a cleanup function that reverts things:
// typically it's best to useLayoutEffect() instead of useEffect() to have React render the initial state properly from the very start. useLayoutEffect(() => { let ctx = gsap.context(() => { // all your GSAP animation code here }); return () => ctx.revert(); // <- cleanup! }, []);
One of the React team members chimed in here if you'd like more background.
We strongly recommend reading the React article at
Here is a fork of your example:
https://stackblitz.com/edit/gsap-react-basic-f48716-xsiulv?file=src%2FApp.js
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Page transition not working as expected in Nuxt 3 was marked as the answer
Hi,
The main issue is that your transition doesn't have a mode, so at some point both elements are in the DOM at the same time, which causes a layout issue, that's why you see the jump or that the animation doesn't happen. If you give your transitions a long time and inspect the DOM you'll see both elements at the same time in the DOM.
That's why we use the mode out-in in our examples:
https://vuejs.org/guide/built-ins/transition.html#transition-between-elements
If you check this simple example created by the Vue team and delete the mode prop in the Transition component, you'll see the issue at a smaller scale. That is exactly what's happening in your example but at a larger scale.
So adding a mode out-in to the transition config object fixes that.
const pageTransition = { name: 'page-transition', mode: 'out-in', css: false, onBeforeLeave: (el) => {}, onLeave: (el, done) => { gsap.to(el, { opacity: 0, duration: 0.4, ease: 'expo.out', onComplete: () => { done(); }, }); }, onBeforeEnter: (el) => { gsap.set(el, { opacity: 0 }); }, onEnter: (el, done) => { gsap.to(el, { opacity: 1, duration: 0.4, ease: 'expo.in', onComplete: () => { done(); }, }); }, }; Finally is worth noticing that you're not toggling the transitionComplete property from the composable, so your ScrollTrigger instances are not being created, just an FYI.
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in ScrollTrigger not working on iOS was marked as the answer
Hi,
Yeah, but we can't debug live sites, is impossible for us to test and check the code in there, plus the demo that you provide does work and it doesn't reflect your real scenario. So anything we check in the demo, might not be portable to your actual project.
Finally is worth noticing that the animation doesn't work neither on an android phone, but just out of curiosity I scrolled all the way to the bottom of the site and then when scrolling up the elements were there. I strongly suggest you to use markers, the first thing you should try when debugging some ScrollTrigger issue is set the markers to true and give the ScrollTrigger instance that is not working a specific ID in order to properly identify it. Oddly enough this does work as expected in a desktop on a screen resized to a phone scale, so there is something else here at play that might not be GSAP related, because this doesn't seem to be related with the screen size or the OS, but something else that is detecting whether or not you are using a device.
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in multiple timelines and reversing from specific labels was marked as the answer
Hi,
You can use a fromTo instance for the headline:
dlTimeline .addLabel('start') .set(dl, { autoAlpha: 1 }) .fromTo(headline, { autoAlpha: 1, y: 0 }, { autoAlpha: 0, y: -100 }) .addLabel('contentin') .from(dt, { autoAlpha: 0, y: 100 }, "<.1") .from(dd, { autoAlpha: 0, y: 100 }, "<.2") .addPause() .addLabel('contentout') .to(dt, { immediateRender: false, autoAlpha: 0, y: -100 }) .to(dd, { immediateRender: false, autoAlpha: 0, y: -100 }, "<.1"); That makes it work in the way you intend and are describing in the two steps to reproduce the issue:
See the Pen GRPZmve by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Draggable object issue with overflow was marked as the answer
Hi @nuratic and welcome to the GreenSock forums!
The issue is that the child elements have an absolute position but their parent is not positioned, so the elements are taken out of the flow.
This seems to solve it:
.listContainer { position: relative; cursor: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/106114/cursor.png), move; cursor: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/106114/cursor.png) 32 32, move; } Finally you are using old GSAP syntax and very old versions of GSAP's Core and Draggable. Any particular reason for that? I strongly recommend you to use the latest versions and check the migration guide:
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Gsap Page transition with preloader logo issues was marked as the answer
Hi @SquareMuse and welcome to the GreenSock forums!
I had a few issues following your code so instead of trying to make it work with your code I created a simple demo instead:
See the Pen jOXEYRm by GreenSock (@GreenSock) on CodePen
Basically the example has both timelines paused and resuming the preloader one after the content is ready (in this case I'm using a dummy ajax call, but in real life you should have something else that indicates that the content is ready). When the preolader timeline is complete it'll resume the slide animation.
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in How to scroll element inside Draggable element? was marked as the answer
Hi @leoarada and welcome to the GreenSock forums!
First is the click event on the red elements that is not GSAP related at all. That will inevitably close the parent element because of a state change in the parent component.
Then you are creating your Draggable instance oustide the GSAP Context scope. When working with react every GSAP instance in a component should be inside the scope of a GSAP Context instance:
useLayoutEffect(() => { const ctx = gsap.context(() => { Draggable.create(/*...*/); }); return () => ctx.revert(); }, []); All you have to do is check the scroll position and enable/disable the Draggable instance:
https://greensock.com/docs/v3/Plugins/Draggable/disable()
https://greensock.com/docs/v3/Plugins/Draggable/enable()
Also I wouldn't recommend doing this:
useLayoutEffect(() => { let ctx; ctx = gsap.context(() => {}); // Later on your code, re-assign ctx to a different GSAP Context instance ctx = gsap.context(() => {}); return () => ctx.revert(); }, []); Because the GSAP instances added to the first assignment of ctx won't get reverted and that could lead to unexpected errors, odd behaviour and memory leaks. Just add stuff to the GSAP context instance using the add() method or even better, as I mentioned before, create every GSAP instance inside the scope of the GSAP Context instance.
Finally try to avoid using top/bottom/left/right in your animations, better use X/Y/xPercent/yPercent in order to use transform and get better performance.
Here is a fork of your codepen:
See the Pen yLGYjgL by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in GSAP ScrollTrigger Circular Image Slider was marked as the answer
Hi,
You could try ScrollTrigger's container animation:
https://greensock.com/3-8/#containerAnimation
Here is a demo:
See the Pen 9be5d371346765a8c9a426e8f65f1cea by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Can't rotate a PixiJs Sprite correctly was marked as the answer
Hi @amicoderozer and welcome to the GreenSock forums!
PIXI display objects are a different thing than regular DOM nodes, that's why transform origin is not having any effect. You have to use a specific property of the PIXI Display object to set the transform point or origin point soto speak. In PIXI that is the anchor property:
http://pixijs.download/release/docs/PIXI.Sprite.html#anchor
This should work in the way you expect:
let pixiBunny = PIXI.Sprite.from(texture); app.stage.addChild(pixiBunny); pixiBunny.cursor = "pointer"; pixiBunny.interactive = true; // This set the transform point pixiBunny.anchor.set(0.5); pixiBunny.scale.set(3); pixiBunny.x = 100; pixiBunny.y = 100; pixiBunny.onmouseover = () => { gsap.to(pixiBunny, { pixi: { rotation: -45 }, duration: 1 }); };
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in ScrollTrigger breaks when container have backdrop-filter: blur() was marked as the answer
Hi,
So the problem is not related to GSAP but to the fact that the filter property creates a problem with position fixed on child elements, which is what GSAP is normally doing for pinning:
https://stackoverflow.com/a/52937920/2456879
One option is to use transform for the pinType:
ScrollTrigger.create({ trigger: ".block", start: "top top", pin: ".text", pinType: "transform", markers: true }); Finally thanks for the super simple and easy to follow demo! 👍
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in ScrollTrigger self.previous().end won't work as expected. Third section won't pin to the end of second one. was marked as the answer
Hi,
Maybe something like this:
See the Pen bGOdpeg by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in ScrollTrigger spacing was marked as the answer
Hi @pylvin and welcome to the GreenSock forums!
The best approach in this cases is to wrap everything that you want to look pinned around an extra container, pin that container and use the horizontal smaller element to trigger the ScrollTrigger instance, like these examples:
See the Pen RwqNXbK by GreenSock (@GreenSock) on CodePen
See the Pen LYMYYEL by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Trying to use ScrollTrigger after the completion of previous ScrollTrigger was marked as the answer
Hi,
The problem is your loader container. You are giving that a huge scale:
.to(".loader__container", { scale: 500, ease: "none", duration: 1 }) The issue is that the loader setion (the parent of that particular element) has a default overflow property and an absolute position, without any particular top/left position:
<section class="loader"> <div class="loader__container"> <h1>LOADER</h1> </div> </section> .loader { position: absolute; clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); width: 100%; height: 100vh; background: black; } So obviously the now humongus loader container uses a lot of space in your document which leads to all that scrollable area. Is always a good idea to test without ScrollTrigger and then without any GSAP animations whatsoever. By commenting out the ScrollTrigger part I was able to spot the problem and isolate the cause:
See the Pen eYbNYJw by GreenSock (@GreenSock) on CodePen
Keep in mind that absolutely positioned elements are taken out of the document's flow, so setting the absolute element to be at top/left zero fixes the issue. If you remove the comment in lines 20-22 of the CSS you'll see it working as expected.
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Animated Continuous Sections with GSAP ScrollTrigger was marked as the answer
Hi,
Indeed in the case of setting the active you're using an onUpdate method which is very expensive because is ran on every slide on every update. Better use a call() method with the position parameter checking the current index and the direction of the ScrollTrigger instance:
slides.current.forEach((slide, index) => { if (index > 0) { slidesTl.current .to(slide, { clipPath: "polygon(0% 100%, 100% 100%, 100% 0%, 0% 0%)" }) .call(() => { const _d = slidesTl.current.scrollTrigger.direction; setActiveNavItem(index + (_d > 0 ? 0 : _d)); }, null, "-=0.5") } }); I stored the timeline in a ref in order to use it in the click event. In that case the click wasn't working because all your slides top position is the same, so obviously the top position calculated by the ScrollTo Plugin will be the same. In this cases the best approach is to use the start and end point of the ScrollTrigger instance and use the current slide index to estimate where the ScrollTo Plugin should tween the window scroll value:
const slideToSection = (index) => { const factor = navItems.current.length - 1; const st = slidesTl.current.scrollTrigger; gsap.to(window, { scrollTo: { y: st.start + ((st.end - st.start) * (1 / factor * index)), }, duration: 1 }); }; Here is a fork of your codepen:
See the Pen jOQpPGO by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Change slider direction on mousemove was marked as the answer
Hi,
I would advice you against modifying the helper function and add that custom logic to the Timeline instance returned by it. Keep in mind that GSAP can animate any numeric property of any object you feed to it and since tweens and timelines are just javascript objects you can tween properties of them, such as timescale.
In this case is better to detect the mouse position and based on the half of the screen the mouse is, switch a direction value and tween the timescale of the timeline returned by the helper function:
See the Pen ZEVYrvm by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Synchronize containerAnimation elements was marked as the answer
Hi,
I see two issues in your setup, first your tag animations that should be controlled by the container animation don't have any property that GSAP should animate:
tags.forEach((tag, index) => { superTimeline.add( gsap.to(tag, { scrollTrigger: { id: tag.id, containerAnimation: superTimeline, start: "center 80%", end: "center 20%", trigger: tag, onEnter: () => { tag.classList.add("tag-visible"); }, markers: true } }) , 0); }); They have just a ScrollTrigger configuration and no animation will happen because of these instances. Also you are adding every single instance at 0 seconds so all of them will start at the same time but at the same time you are passing start and end points.
Second, you are adding these instances to the superTimeline, which at the same time is being used as the container animation property. That's logically impossible, since every time you add an instance to the timeline the timeline duration gets longer and therefore the ScrollTrigger calculations get really messy. On top of that you are nesting single tween instances with ScrollTrigger configuration to a Timeline that also has a ScrollTrigger configuration, that is another logical pickle that will create more issues down the road:
Nesting ScrollTriggers inside multiple timeline tweens
A very common mistake is applying ScrollTrigger to multiple tweens that are nested inside a timeline. Logic-wise, that can't work. When you
nest an animation in a timeline, that means the playhead of the parent timeline is what controls the playhead of the child animations (they all must be synchronized otherwise it wouldn't make any sense). When you add a ScrollTrigger with scrub, you're basically saying "I want the playhead of this animation to be controlled by the scrollbar position"...you can't have both. For example, what if the parent timeline is playing forward but the user also is scrolling backwards? See the problem? It can't go forward and backward at the same time, and you wouldn't want the playhead to get out of sync with the parent timeline's. Or what if the parent timeline is paused but the user is scrolling?
So definitely avoid putting ScrollTriggers on nested animations. Instead, either keep those tweens independent (don't nest them in a timeline) -OR- just apply a single ScrollTrigger to the parent timeline itself to hook the entire animation as a whole to the scroll position.
Then you have this:
superTimeline.add(animateBackground(background, offset)); superTimeline.add(animateTags(tags, offset), 0); function animateTags(tags, offset) { return gsap.to(tags, 2, { x: -offset, ease: "power4.easeOut" }); } So you want to animate the tags using container animation but you are also animating the tags in the timeline you are using as the container animation and you are adding all the animations at zero seconds of said timeline. Again you are creating instances and animations in the timeline that should do just two things:
Horizontally move a group of elements or a single element Serve as an indicator for the containerAnimation property to when create specific animations I created a super simple fork of your codepen so you can see how it actually is supposed to work:
See the Pen ExOrvWX by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in getting each video to play/pause at a certain point was marked as the answer
Hi,
You are looping through an array of DOM elements so each video in your loop is a DOM node, not an array-like object, so the fact that video[0] is undefined is to be expected.
videos.forEach(() => { video // -> DOM element videos[0] // first DOM element in the videos array }); Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Scroll trigger - Change content when scrolling on pinned element was marked as the answer
Hi @_elizabeth and welcome to the GreenSock forums!
Is not the same setup to mix Observer and ScrollTrigger. The example you linked from the GreenSock collection uses only Observer.
This example seems to do what you're trying to achieve:
See the Pen oNdNLxL by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Different morphing behavior between MorphSVGPlugin and kute.js was marked as the answer
Hi @Byron Shi and welcome to the GreenSock forums!
Thanks for being a Club GreenSock member and supporting GreenSock! 💚
This boils down mostly to preparing your shapes. Normally a circle (shape), when is converted to a path it has only four anchor points (top, bottom, left and right) while the shape you have (hexagon) has 6 anchors, so the MorphSVG plugin has to remove those extra points and move the paths accordingly.
The best solution is to create your circle, add those extra points and remove the ones that are not needed so it ends up looking like this:
In the image you can see te gray squares, those are the anchor points of the circle and they match the hexagon's anchors perfectly and here is the result:
See the Pen VwqZwwX by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in how can i autoRotate in motion path container without affecting inner images was marked as the answer
Hi @shadysw and welcome to the GreenSock forums!
Based on the setup you already have and the fact that the entire element (that contains the image and the dots) is the one being animated along that path, is not going to be possible, unless you start rotating the image to counter the effect of the path's rotation.
What you could do is get the dots in the target element and rotate them independently as you tween the progress of your timeline here:
function moveWheel(amount, i, index) { let progress = tl.progress() tl.progress(wrapProgress(snap(tl.progress() + amount))) let next = tracker.item tl.progress(progress) setClasses(items, next) gsap.to(tl, { progress: snap(tl.progress() + amount), modifiers: { progress: wrapProgress, }, }) // Create another tween to rotate the dots } That should be the simplest way to approach this IMHO.
Here is a fork of your codepen:
See the Pen JjwPGOq by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
-
Rodrigo's post in Help with text pinning animation was marked as the answer
Hi,
I'm not 100% sure of what you're trying to do. This is my best guess based on your description:
See the Pen QWJXyOV by GreenSock (@GreenSock) on CodePen
Hopefully this helps. If this is not what you're after, please be more specific about what you're trying to do.
Happy Tweening!
-
Rodrigo's post in Scaling SVG clipPath not re-centering on resize was marked as the answer
Hi @redtangle and welcome to the GreenSock forums!
This is a kind of working example of your codepen:
See the Pen rNQgvgx by GreenSock (@GreenSock) on CodePen
Unfortunately SVG is not something I'm well versed so there is not a lot of help I can provide on the subject. The main problem is that the timeline was being invalidated on the refresh method but with the previous position of the element.
I wasn't able to make it work with the triangle, maybe because of the original position in the SVG path or something else. As I mentioned SVG is not something I have a ton of knowledge and I don't have the time to dig into it and see what could be the issue. Maybe draw the triangle in the top left corner and then position it using GSAP.
Hopefully this helps.
Happy Tweening!
-
Rodrigo's post in Changing the x transform position of two elements was marked as the answer
Hi,
Maybe something like this:
See the Pen ZEmNRLO by GreenSock (@GreenSock) on CodePen
Hopefully this helps.
Happy Tweening!