Jump to content

GreenSock last won the day on January 17

GreenSock had the most liked content!


  • Posts

  • Joined

  • Last visited

  • Days Won


GreenSock last won the day on January 17

GreenSock had the most liked content!

About GreenSock

Profile Information

  • Gender
  • Location
    Chicago Area
  • Interests
    Volleyball, Basketball, Christian Apologetics, Motorcycling

Recent Profile Visitors

87,286 profile views

GreenSock's Achievements

  1. I think that was just a way to allow you to access the GSAP tween, that's all. I'm not a TypeScript pro - you may need to adjust your definitions if that's causing a problem. It's just a helper function, after all, it's not intended to be a full TypeScript-enabled tool
  2. I guess you could try setting display: inline-block on the <span> to make them work with transforms. I’m half-conscious in bed with COVID on my iPad, so I can’t verify at the moment, but you could give it a shot.
  3. Why would an empty style tag cause a bug? ScrollTrigger has to set inline styles temporarily during refresh() in order to ensure things are measured correctly and then it removes them of course. I’m baffled as to why that would cause any problems for you. I think it also sets scroll-behavior to make sure it’s not “smooth” which would break things.
  4. I believe that's because when you created your GSDevTools instance, your timeline was completely empty! So there's no duration, no animations in it, nothing. See what I mean? It's definitely best to create it after you've populated your timeline/animation. 👍
  5. Did you try this suggestion? All the clues you've offered thus far very much sound like you're calling a .from() tween multiple times. For example, you said it stops part-way through the from() animation. That's a dead giveaway. Here's where I explained it: I added one line of code to this demo that should protect against the double-call logic problem: this.ctx && this.ctx.revert(); https://codepen.io/GreenSock/pen/eYjeNXa?editors=0010 It's very difficult to troubleshoot a live site. If you'd like more help, please provide a minimal demo that clearly illustrates the issue (like a CodePen or CodeSandbox or Stackblitz) and we'd be happy to take a look. But I'm pretty sure you're double-calling your gsap.from() stuff.
  6. That sounds like a logic issue that would occur if you run that from() call MULTIPLE times quickly. I'd recommend putting a console.log() right before you create your animations and see if it's getting called multiple times. I bet it is. React 18 started calling useEffect()/useLayoutEffect() TWICE in strict mode by default which really messed a lot of people up. I'm not familiar with Vue, but I wonder if something similar is happening there. .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: // 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 call revert() in a cleanup function (like maybe unmounted() in Vue?) mounted() { this.ctx = gsap.context(() => { // all your GSAP animation code here }); }); unmounted() { this.ctx.revert(); } I also simplified some of your code in this fork: https://codepen.io/GreenSock/pen/yLqzGGP?editors=0010 Does that help?
  7. Ah, okay. Another fun challenge for me! 🥳 Here's a helper function that lets you feed in a target element (like your headline) and a motion path and it'll spit back the progress value (between 0 and 1) corresponding to where it'll hit the center of that target element on the given axis ("y" axis by default): // helper function that returns the progress value for a motion path where it hits the center of the provided target on the given axis ("y" by default). function findProgress(target, path, {axis="y", precision=1, ease="none"}={}) { target = gsap.utils.toArray(target)[0]; path = gsap.utils.toArray(path)[0]; ease = gsap.parseEase(ease) || (p => p); let tBounds = target.getBoundingClientRect(), pBounds = path.getBoundingClientRect(), useX = axis === "x", tCenter = (tBounds[useX ? "left" : "top"] + tBounds[useX ? "right" : "bottom"]) / 2, rawPath = MotionPathPlugin.cacheRawPathMeasurements(MotionPathPlugin.getRawPath(path), Math.round(precision * 12)), start = rawPath[0][useX ? 0 : 1], end = rawPath[rawPath.length - 1][rawPath[rawPath.length-1].length - (useX ? 2 : 1)], pinpoint = gsap.utils.mapRange(pBounds[useX ? "left" : "top"], pBounds[useX ? "right" : "bottom"], start, end, tCenter), l = Math.round(precision * 200), inc = 1 / l, i = 1, prevV = start, p, v; if (pinpoint < Math.min(start, end)) { p = start < end ? 0 : 1; } else if (pinpoint > Math.max(start, end)) { p = start < end ? 1 : 0; } else { for (; i < l; i++) { p = i / l; v = MotionPathPlugin.getPositionOnPath(rawPath, ease(p))[axis]; if ((v >= pinpoint && prevV < pinpoint) || (v <= pinpoint && prevV > pinpoint)) { return p - (1 - gsap.utils.normalize(prevV, v, pinpoint)) * inc; } prevV = v; } } return p; } Here's a demo where I just placed a horizontal blue bar 1300px from the top (change the CSS to whatever you want) and it'll set the progress of the motionPath so that the tractor is right at that spot: https://codepen.io/GreenSock/pen/BaPdrKM?editors=0110 If it's not clear, please provide a minimal demo (like a CodePen) with your setup simplified as much as possible, and I don't mind wiring it up for you. I hope this helps you (or someone else)
  8. That's because of this: end: "+=1" That means it toggled the class but if you scrolled more than 1 pixel past the start, it toggled it back again. Your entire ScrollTrigger only lasts 1 pixel. I assume you meant something more like this?: https://codepen.io/GreenSock/pen/jOpGvJL But even that seems odd to me; why are you even toggling that class at all if it's gonna be on the entire time (start: 0, end: 99999)? Why not just apply that class directly and leave it there (no ScrollTrigger involvement)?
  9. I saw a few problems: You're setting the width/height in the same gsap.set() call as the transformOrigin, and in this very rare edge case that's actually a problem because it just so happens that in the for...in loop through the properties, transformOrigin happens BEFORE the width/height. So when it tries to calculate the percentage-based origin offsets, your <rect> literally has no width or height at all, thus it gets positioned in its upper left corner. The solution: set the width/height FIRST. You could just separate those out into their own gsap.set() that you put first. You're creating conflicting animations. If you click again before the first set of animations completely finishes their 4 repeats, you'll be creating new ones that are also fighting with the old ones for control of the same elements. Make sure you kill() the old animations before you create new ones. Or you can just leverage the overwrite feature (overwrite: true here). Just so you know, the smoothOrigin does absolutely nothing in this line: gsap.timeline({ smoothOrigin: true, yoyo: false, repeat: 4 }); Timelines don't have a property like that. Maybe you intended to pass that down as a default for all child tweens?: gsap.timeline({ defaults: {smoothOrigin: true}, yoyo: false, repeat: 4 }); https://codepen.io/GreenSock/pen/NWBaLOd?editors=0010 Does that clear things up?
  10. Sorry, I didn't understand your question @Abdullah al Mahmood. Can you please restate? And you'll significantly increase your chances of getting a good answer if you provide a minimal demo (like a CodePen) that clearly illustrates the problem.
  11. Hey @chrisgannon! It looks like Rodrigo beat me to it. Yeah, you're using the current release of the GSAP core with a pre-release (beta) version of Draggable Just use the current release of Draggable and you should be golden. Beta (not released): https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/Draggable3.min.js Current release: https://unpkg.com/gsap@3/dist/Draggable.min.js Or it would work to use the beta version of the upcoming release of both files (core and Draggable). The beta version of the core is at https://assets.codepen.io/16327/gsap-latest-beta.min.js Just don't mis-match them, that's all.
  12. We're happy to answer GSAP-specific questions, but unfortunately logic/plumbing/architecture stuff is a bit beyond the scope of what we typically provide here (see the forum guidelines). You'll definitely have a better chance of getting answers if you break things up into one question at a time (ideally with a minimal demo) rather than one post with a whole bunch of questions crammed in That being said, I'll take a crack at some of your questions... Sorry, I don't understand the question. Maybe some pseudo code would help. A minimal demo is even better. A gsap.context() is only for making it easier to revert() a whole group of GSAP-related stuff (which is very different than killing it - reverting actually returns things to their original states and inline styles whereas killing an animation simply stops it from affecting things any further, leaving the state as-is). Don't think of it like a controlling mechanism. Timelines are perfect for that type of thing. It is difficult to give blanket advice for all situations. If you just need to be able to check a timeline to see if it's in-progress, you can use its isActive() method. In some cases, it can be useful to have a boolean variable that you set in an onStart/onComplete. Sometimes you need to accommodate multiple states, so it's not a boolean - the variable may store something like "about"/"learn"/"features". My personal preference is to modularize things as much as possible to avoid spaghetti code, so maybe you create a Menu object that is in charge of handling state changes, and it exposes methods like .goto(state) so you can say menu.goto("about") and internally, it handles coordinating that, tracking its own state, etc. It just seems cleaner to me to have things be object-oriented. If you need to wait until an in-progress timeline (for example) finishes before doing something else, there are several ways to do it... You could just figure out the remaining time and use that as a delay for the next animation: gsap.to(...{ delay: tl.duration() - tl.time(), ...}); // time left You could set an onComplete dynamically tl.eventCallback("onComplete", () => {... do stuff...}); You could just kill() the in-progress animation and redirect values to wherever you want from there. Don't feel like you need to always reuse the same timeline instance. That has pros and cons. It's often best to just create a new timeline each time you want to animate something so that the values get redirected dynamically. That's up to you. In my opinion, it's best to write your code to accommodate interruptions cleanly. What if someone clicks, that creates a 2-second animation...but the user clicks again after 1 second? My pet peeve is when the app IGNORES my click, like "nope, I must finish my entire animation before I allow you to do anything". In my opinion, it should gracefully handle that. So debouncing mouse clicks may only prevent a problem if the clicks happen SUPER fast, but that's only one scenario. If you code it to handle clicks anytime gracefully, then you don't need to worry about debouncing. In my experience, I typically see people way over-complicate menus like this or they're plagued with a lot of spaghetti code or they try to create every animation ahead of time and juggle all the different instances rather than just dynamically animating the state on-demand with new animations. I hope that helps!
  13. That attribute won't make any difference whatsoever with GSAP/ScrollTrigger. It's totally fine. If you still have problems, feel free to fork my demo and reproduce the problem and post it back here with your question. Good luck! And yes, you might want to consider ScrollSmoother which is nicely integrated with ScrollTrigger. 👍
  14. The dead giveaway was this attribute: data-scroll-container One problem I saw right away was: trigger: pinContainerRef.current, start: "top bottom", scrub: 1, pin: pinContainerRef.current So you're telling it to pin the element when the top of the element hits the BOTTOM of the viewport, meaning it is not even in the viewport yet. It'll be pinned down below, so you can't even see it while it's pinned. You also have scroller: ".App" which means you're telling ScrollTrigger to only watch for scroll events on that ".App" element...but in your demo, that thing isn't the scroller. It's the main document/body that has the scrollbar. By the way, you don't need to wrap stuff in a css: {} object. That's from like 2012 Here's a quick fork: https://codesandbox.io/s/keen-solomon-wdk6md?file=/src/pages/Homepage/sections/About/index.jsx