Jump to content
Search Community

GreenSock last won the day on April 21

GreenSock had the most liked content!

GreenSock

Administrators
  • Posts

    23,142
  • Joined

  • Last visited

  • Days Won

    817

Posts posted by GreenSock

  1. 1 hour ago, ElephasHyd said:

    For some reason refreshPriority is not working for me even while increasing the values dynamically.

    Got a demo illustrating that? I wonder if you're not setting refreshPriority properly. Perhaps you've got it inverted or you've got overlapping values. 🤷‍♂️

     

    1 hour ago, ElephasHyd said:

    Wow, this works out of the box for me

    🥳

  2. Yep, same issue - you're creating things out of order, thus they refresh in the wrong order. For example, let's say elementA is 100px from the top of the screen, and there's a ScrollTrigger that triggers when that hits the top of the screen ("top top"). So normally, the start would be 100. But what if there's another ScrollTrigger that pins an element above that one for 1000px - that'd push everything down, thus that element should trigger at 1100px instead of 100px. If ScrollTrigger calculates them in the wrong order, it'd set the first one to a start of 100px (because the pinning one hasn't been factored in yet). 

     

    Here's a helper function that you can call after all of your elements are in place, and it'll order things based on their proximity to the top of the viewport: 

    function verticalSort() {
      let scroll = window.pageYOffset;
      ScrollTrigger.getAll().forEach(t => t._sortY = t.trigger ? scroll + t.trigger.getBoundingClientRect().top : t.start + window.innerHeight);
      ScrollTrigger.sort((a, b) => a._sortY - b._sortY);
    }

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

     

    Better? 

     

    Of course you could solve everything by explicitly stating the unique refreshPriority for each, but the above function seemed easier and it should work in most cases. 

    • Like 2
  3. Yes, like @Rodrigo said, you're creating your ScrollTriggers out-of-order. You're supposed to create them in the order they would be encountered (top to bottom). You're creating the top and bottom first, then the middle, so the refreshing order goes: 1, 3, 2 instead of 1, 2, 3. 

     

    For relatively simple setups, it could be adequate to just call ScrollTrigger.sort() which will order them by whatever their "start" is calculated to be. But you can explicitly control the order of things by setting a refreshPriority on each one so you have total control of the order. 

    See the Pen PogLyGO?editors=1010 by GreenSock (@GreenSock) on CodePen

     

    And here's a verticalSort() helper function that'll sort them by their proximity to the very top of the viewport: 

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

    • Like 1
  4. Yeah, I don't understand what you're trying to do there, @m__shum. Reverting happens immediately, there's no need to wait for a tick. But you mentioned reverse() (which isn't in your demo) - are you trying to have an animation play in reverse and then after it's done (playhead reaches the start), you want to revert() it? If so, just use onReverseComplete to fire off a function that reverts. 

     

    It'd be super useful if you provided a minimal demo showing exactly what the problem is. Maybe I'm missing something obvious in your original demo there. 

  5. Yeah, that definitely seems like a Firefox rendering bug, totally unrelated to GSAP. My guess is that the renderer needs time to create the raster image for each SVG image. It likely ignores them completely (skips creating the raster) initially because they are display: none. But once it flips to display: block once, that raster image gets created and cached by Firefox, thus it can show it faster next time. 

     

    Maybe try setting ALL of the images to display: block initially, just for 1 tick maybe so that the browser gets them all rasterized/cached. Rodrigo's suggestion to go with canvas is very good too. 

    • Like 1
  6. Unfortunately it's just not feasible to make ScrollToPlugin accommodate that automatically. There's no way it could understand which elements are affected by which ScrollTriggers. It's essential that you, as the builder of the page, provide that information. But here's a helper function you could try - it lets you create several lookups that get added together into one big lookup. Splitting them up like this allows you to segregate elements according to whether or not they're in a containerAnimation and/or pinnedContainer: 

     

    function getScrollLookup(targets, {start, pinnedContainer, containerAnimation}) {
      targets = gsap.utils.toArray(targets);
      let initted,
          triggers = targets.map((el, i) => ScrollTrigger.create({
            trigger: el,
            start: start || "top top",
            pinnedContainer: pinnedContainer,
            refreshPriority: -10,
            onRefresh(self) {
              if (initted && Math.abs(self.start) > 999999) {
                triggers[i].kill();
                triggers[i] = ScrollTrigger.create({
                  trigger: el,
                  start: start || "start start",
                  pinnedContainer: pinnedContainer,
                  refreshPriority: -10,
                });
              }
            },
            containerAnimation: containerAnimation
          })),
          st = containerAnimation && containerAnimation.scrollTrigger,
          lookups = [],
          lookup = target => {
            let t = gsap.utils.toArray(target)[0],
                i = targets.indexOf(t);
            if (i < 0) {
              for (i = 0; i < lookups.length; i++) {
                if (lookups[i].targets.indexOf(t) !== -1) {
                  return lookups[i](t);
                }
              }
              return console.warn("target not found", target);
            }
            return triggers[i].vars.containerAnimation ? st.start + (triggers[i].start / containerAnimation.duration()) * (st.end - st.start) : triggers[i].start;
          };
      lookup.targets = targets;
      lookup.add = (targets, config) => {
        lookups.push(getScrollLookup(targets, config));
        return lookup;
      };
      initted = true;
      return lookup;
    }

     

    So you'd use it like: 

    let lookup = getScrollLookup("section.project", { containerAnimation: tween, pinnedContainer: el });
    lookup.add("section.other", {}); // no containerAnimation or pinnedContainer
    lookup.add("section.pinned", {pinnedContainer: el}); // just a pinned container
    
    // then later...
    let position = lookup(".any-of-the-above-elements");

    Hopefully that helps. 

    • Like 1
  7. Yeah, I would never expect CSS and JS animations to remain synchronized. They use completely different timing mechanisms and threads. GSAP has lag smoothing too, but CSS wouldn't match that. In short, if you're trying to build something such that CSS and JS animations are 100% perfectly synchronized, I'd give up immediately and adjust your strategy. Obviously I'd recommend just using GSAP because you'll get way more flexibility overall and everything will remain synchronized. 

  8. One other thing you could try on mobile is ScrollTrigger.normalizeScroll(true) to force the scrolling to happen on the main thread. Does that help at all? You could also use ScrollSmoother with a very small smoothTouch value. (That requires a Club GSAP membership though)

  9. I'm not able to replicate the issue, but I noticed a few things: 

    1. This doesn't make sense: 
      start: "bottom top",
      end: "bottom bottom",

      The bottom of the element would hit the bottom of the viewport BEFORE it hits the top, thus you're setting it up so that your start is AFTER your end (inverted). 

    2. You're creating your timeline OUTSIDE of your matchMedia(), and adding a tween inside. That's generally a bad idea - don't you want it to clean up the whole timeline if that matchMedia() reverts? I mean what's the point of you creating an empty timeline when that matchMedia() doesn't match anyway? 

    3. Instead of animating to width: auto (which isn't even numeric, making it difficult to interpolate), why not make it a function-based value that you calculate a pixel value on? Or better yet, since it just conforms to the child <svg> which you explicitly set to 137px, why not animate to that specific value? 

    4. I would not recommend animating the generic "margin" value because that is composed of several values. Instead, animate the individual parts that you want. 

      // BAD
      margin: "50px 0 0 0"
      
      // GOOD
      marginTop: "50px"

     

    I hope that helps.

  10. 9 hours ago, justintime said:

    Did I miss it in the documentation? If it's not there, it would be perfect to know what the parameters are for the different stock css easings.

    No, those values were just taken from the tool that @Rodrigo pointed you to previously. I have no idea where Matthew got those numbers. They're likely an approximation. 

     

    Your goal is to create a GSAP animation that eases in EXACTLY the same way as a CSS animation? Why? That seems very strange to me. If your goal is to have them perfectly synchronized, I don't think that's even possible to do because of the way browsers work. Why would you not just use GSAP? It gives you much, much more power and flexibility that CSS. 🤷‍♂️

  11. I noticed two problems:

    1. You nested ScrollTriggers inside a timeline. That's logically impossible to accommodate - the playhead of a tween can't be controlled by BOTH a timeline's playhead AND the scroll position simultaneously. 
    2. You've got multiple tweens of the same element controlled by ScrollTriggers. By default, animations with ScrollTriggers are rendered immediately. So in your case, you just need to set the 2nd and 3rd to immediateRender: false so that they don't step on each other. 

    Is this what you're looking for?:

    See the Pen QWPzBXO?editors=1010 by GreenSock (@GreenSock) on CodePen

     

    And alternative might be to create a timeline that has ONE ScrollTrigger applied to the timeline itself (not nested ones), and have all your animations in there. That'd assume you can just make that one timeline span across the entire scroll area that you need. If not, then just stick with the individual tweens. 

     

    I hope that helps. 

  12. Yeah, if you check the console there's a warning: 

    Quote

    Warning: <path> length cannot be measured when vector-effect is non-scaling-stroke and the element isn't proportionally scaled.

    It's just not something that can be accommodated at this point - you've got your SVG set up to scale in a non-uniform way. Sorry, I wish there was a magic bullet I could offer. 

  13. I noticed several problems: 

    1. You had faulty markup (missing closed </div> tags)
    2. You were defining a containerAnimation for the getScrollLookup(), but only some of the elements were in the containerAnimation.  That is not valid.
    3. You were pinning a container element, but you didn't define that in the getScrollLookup() config. 

    Is this more of what you wanted?

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

    • Like 1
  14. I believe that's the issue that was already reported and patched in this thread related to once: true

     

    The patch hasn't been released yet, but it's very easy to work around by just creating a simple ScrollTrigger first (it doesn't need to do anything). 

     

    https://stackblitz.com/edit/stackblitz-starters-vynow2?file=src%2Fcomponents%2FHeroIntro%2FHeroIntro.js

     

    Does that clear things up? 

  15. This appears to be funky behavior caused by Vue forcing refs to be Proxy objects. So when you access ref.value, it isn't the real object at all! It's a Proxy which alters what "this" refers to inside method calls. I think it's terrible that Vue does this actually, but maybe they have good reason for it. 🤷‍♂️

     

    From what I can tell, the solution would be to use a shallowRef() instead of ref():

    https://stackblitz.com/edit/github-vfgcdf-g52l6b?file=app.vue

     

    Does that clear things up?

    • Like 1
  16. Without a minimal demo, it's super difficult to offer a suggestion here or understand what you're asking for - are you saying you want to apply ScrollSmoother...but not have it actually work at all? You just want to have it pretend to work, feeding you the value that it would apply to the container's transform, without applying it? Maybe you could add a ticker listener with a high priority to grab the original transform, and then use an onUpdate on the ScrollSmoother where you grab the new value but immediately apply the old one to revert it(?) I'm totally guessing here in the absence of a minimal demo, but hopefully that gives you a little to go on at least. I wonder why you wouldn't adjust whatever you're doing with the <body> transform to more cleanly apply that in a way that integrates with ScrollSmoother(?) 

  17. Does it work well for you without Lenis? That's not a GreenSock product, so we can't really support it here. 

     

    If you're talking about the pin seeming to be a little bit tardy, that's likely because mobile browsers are particularly bad with using a separate, non-synchronized thread for scrolling which is why ScrollTrigger offers a anticipatePin option. You might want to try adding anticipatePin: 1 or some other value just to see if it helps. You could also see if it's any better with ScrollTrigger.normalizeScroll(true);

  18. I assume maybe you mean something like this?: 

    let tween1, tween2;
    function invalidateAndRemakeAnimations() {
    	// Kill existing animations
    	tween1 && tween1.revert();
    	tween2 && tween2.revert();
    
    	// Remake the first animation
    	tween1 = gsap.to(".row._1", {
    		scrollTrigger: {
    			trigger: ".row._1",
    			start: "top top",
    			scrub: true,
    			pin: true,
    			pinSpacing: false,
    			invalidateOnRefresh: true,
    			markers: true
    		}
    	});
    
    	// Remake the second animation
    	tween2 = gsap.to("h2.text", {
    		scrollTrigger: {
    			trigger: ".row._3",
    			start: "top top",
    			scrub: true,
    			invalidateOnRefresh: true
    		},
    		position: "relative"
    	});
    }

     

×
×
  • Create New...