Jump to content


  • Posts

  • Joined

  • Last visited

1 Follower

About RJWadley

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

RJWadley's Achievements

  1. Thanks! Love the deep dive you attached. Makes a lot more sense, and I think we're on the same page now. Here's a real zero duration from tween I've yoinked from a scrubbed timeline in one of our codebases. // raw code from the file timeline.from(shutter, { opacity: 0, pointerEvents: "none", duration: 0, }, 1.5) // a slightly clearer way to write this timeline.fromTo(shutter, { autoAlpha: 0, }, { autoAlpha: 1, duration: 0, }, 1.5) The intent here is for shutter to be hidden until the specified point in the timeline, at which point it should instantly appear. Put more imperatively: - initially, apply the start state. - when passed in the forwards direction, apply the end state. - when passed it the backwards direction, apply the start state. Is the best way to handle situations like this just to use an infinitely small duration, or is there a better way? Also, if you can think of any, is there anything that an infinitely small duration can't do that a zero duration can (specifically in the context of scrubbed/reversed timelines)?
  2. Ah, that does help me understand it. A bit unintuitive, but I'm sure it's probably like that for a reason. It's definitely a bit of an edge case, since it's only instant tweens. I definitely think it would be worth putting some thought into what the behavior should be in this case. I think changing changing the duration should only impact timing, not behavior. It's also a recurring cause of animation bugs on my team, even those of us who've been working with GSAP for years. Then again, I'm nowhere near as experienced in animation as any of you guys are, so if you decide it genuinely should be this way then I'll trust that. Here's another fun example I made while trying to figure out the behavior. It does a better job of showcasing the different behaviors in each of the possible cases. https://codepen.io/RJWadley/pen/BaqbyEV?editors=0010
  3. I ran into some trouble today using .set in a scrubbed timeline. Everything works on the first scroll, but when I scroll back to the top the instant tweens stay stuck at the end. I can work around this by using a really small duration (like 0.001) on a regular .to call, but I wouldn't expect there to be a big behavior difference between a duration of 0 and 0.001. Is this the intended behavior or is it a bug?
  4. If GSAP is imported into at least one file, it is then defined in every file including where it shouldn't be. This also happens to all the plugins. Here's a code sandbox that hopefully illustrates the issue a bit better: https://codesandbox.io/s/gsap-import-types-p88m1x?file=/src/App.tsx (btw, you need to open both files, see the note in the sandbox for why) In that sandbox, I would expect a type error at compile time but instead get a reference error at runtime. The setup here is pretty minimal, but have I set up GSAP incorrectly here?
  5. Thanks for the reply, Rodrigo. Refreshing ScrollTrigger after the smoother is initialized does not fix the issue. This would be the preferred solution for me if it worked. Relying on some React state to recreate everything if the smoother changes seems like it would work until the underlying issue is fixed, although I don't anticipate that being very nice to performance. Something else I found that works is adding a reverse and a play to the enter trigger, although this will still break if the page isn't at the top when everything's initialized. onEnter: () => { console.log("onEnter TRIGGERED"); timeline.reverse(); timeline.play(); },
  6. If you initialize ScrollSmoother after you create a tween or timeline with a ScrollTrigger, the tween doesn't work correctly. It will either get stuck at the beginning or snap to the end. Plus, if the page is not at the top when initialized, onEnter and onLeave are sometimes fired instantly. In React, effects in components higher in the tree are run later, and since ScrollSmoother is usually in one of the highest components it will always be initialized last, which causes this issue.
  7. If a smoother hasn't been initialized, `ScrollSmoother.get()` will return undefined. This should be reflected in the typing of the get function. The return type is currently `ScrollSmoother`, but it should be `ScrollSmoother | undefined`
  8. It seems like fromTo tweens aren't always reverting in the way I would expect. If I use a from or a to tween, the inline styles set by gsap are removed. However, with a fromTo tween, the element is reset to the beginning of the tween and the inline styles are not reset. For example, in the codepen provided I would expect the box to return to the top left when reverted, but it just returns to the beginning of the tween. Am I missing something or is this a bug?
  9. ScrollSmoother works really well in a desktop environment, but I've run into some issues trying to adapt pinning code to also work well on mobile. I haven't seen anybody on the forum talk about this yet, so I thought I'd share my experience and a solution I've found. On desktop, ScrollSmoother is transforming the entire page, so the default pinType of "transform" works best. On mobile, ScrollSmoother turns off and the page scrolls normally, so the default pinType causes sync jitter. A pinType of "fixed" would work really well here, but that will only work if we haven't initialized ScrollSmoother at all (since ScrollSmoother leaves a transform behind even when disabled). Here's an example that works great on desktop, but jitters on mobile: https://codepen.io/RJWadley/pen/JjpaaYR One potential solution here (and the one that most sane people probably choose) is to enable normalizeScroll. That moves scrolling to the JS thread, which fixes the sync jitter. But it's not always a perfect solution (and in all fairness, there is no "perfect" solution, especially with fruit-based devices). Anyway, decided I just wasn't satisfied with it. Another solution is to always use a fixed pin with pinReparent enabled, but that's not ideal for performance and can break styles, so I'd rather keep that as a last resort. In my mind, a better solution is to just not enable scrollSmoother at all on mobile devices, then choosing pinTypes based off of that. In hindsight it's a very obvious solution, but it took me quite a while to figure it out. A simple util like const usingSmoothScroll = !matchMedia("(hover: none)").matches; can help us determine the device type. And we can use the same util to determine which pin type to use as well: pinType: usingSmoothScroll ? "transform" : "fixed" From there, only create a ScrollSmoother instance if usingSmoothScroll is true: const usingSmoothScroll = !matchMedia("(hover: none)").matches; if (usingSmoothScroll) ScrollSmoother.create({ smooth: 1, smoothTouch: 0.1 //we don't want scrollsmoother to entirely ditch the smooth, that would break our pins }); Here's the same example as above, but employing this technique: https://codepen.io/RJWadley/pen/MWVEzVg We get the buttery goodness of ScrollSmoother on desktop devices, and the firm beauty of fixed pins on mobile. All around a pretty good solution. Keep in mind that if the media query in the util ever changes (like if a 2-in-1 enters tablet mode), nothing will update. And if ScrollSmoother ever picks up the ability to hotswap between smooth and smoothTouch (right now it just picks one and never changes), this would probably need significant changes. I think an overall cleaner solution would a new pinType (maybe "smoother" or "smoother-auto") that uses fixed if smoother is off and transform if smoother is on. In order for that to work, ScrollSmoother would also need to remove any transforms from smooth-content when it disables. But that's entirely up to the gsap devs.
  10. As of now the fix hasn't been deployed. It'll be in the next version of GSAP, most likely either 3.10.5 or 3.11.0 If you can, it's easiest to just wait for the update. If that's not an option, I'd say either use the beta files or Damian's solution. As a temporary measure either should be fine.
  11. I was easily able to reproduce the issue with React 17, although I wasn't able to recreate it using only React. And although I wasn't able to determine any causes, I was able to find a solution. I'll drop it here in case anybody encounters the same issue. I discovered that the issue only occurs if I explicitly create a smooth-wrapper and smooth-content like so: () => { SmoothScroller.create() //this doesn't work return ( <div id="smooth-wrapper"> <div id="smooth-content">{children}</div> </div> // ); //if I remove the explicitly created wrappers, this would work fine //but we would lose control of fixed elements (since they'd no longer be outside the wrapper) return children; } To fix the issue, I moved the ScrollSmoother.create() into gatsby-browser while leaving the smooth-wrapper and smooth-content in place. This fixes the jumping while retaining control of fixed elements, however the scroller won't always be up-to-date with the wrapper and therefore sometimes just won't scroll. This can be resolved pretty easily by adding a useEffect that refreshes the scroller anytime the scroll component updates. That leaves the final solution as follows: //in gatsby-browser SmoothScroller.create() //in Scroll component () => { useEffect(() => { SmoothScroller.get().refresh() }) return ( <div id="smooth-wrapper"> <div id="smooth-content">{children}</div> </div> // ); }
  12. So I'm using Gatsby with ScrollSmoother, and the first time you click the page, it instantly scrolls to the top (without any easing). This only happens on the very first click after loading the page. Does anybody have any insight into why this might be happening? I've created a minimal demo of the issue at: https://codesandbox.io/s/beautiful-wind-yn4yyt?file=/src/components/Scroll.tsx To reproduce: Reload the page Scroll down a bit Click anywhere (any mouse button)