Jump to content
Search Community

Absolute positioned element inside scrollTriggered element

Nwpn test
Moderator Tag

Go to solution Solved by Rodrigo,

Recommended Posts

I animate some elements via scrollTrigger and everything is fine.  The problem is that I wish to have some nested modal windows inside the animated elements to be opened/closed via click. Obviously the modal windows should not be displayed inside the parent element but positioned absolutely inside the parent section. But scrollTrigger adds positions to the animated elements, thus making it impossible to position those modal windows relatively to the parent section.

 

I can’t think of a way to avoid this. It would be very useful to keep the html structure as is, because the input would be like that.

See the Pen MWxrrvv by nwpn (@nwpn) on CodePen

Link to comment
Share on other sites

Hi @Nwpn and welcome to the GSAP Forums!

 

Thanks for being a GSAP Club member and supporting GSAP!

 

The button in your demo is not doing anything, what's exactly the issue you're having? Please make sure that the demo you post clearly demonstrates the problem you're encountering, otherwise is really hard for us to know exactly what the problem could be and how to help you with it.

 

My first attempt to something like this would be to create a fixed modal at the top of the document in order to be able to add content and animate it in and out using GSAP, while setting the body element's overflow to hidden to prevent any scrolling while the modal is opened.

 

If you have problems with positioning the elements, you can try the pinType property in ScrollTrigger and set it to transform:

pinType
"fixed" | "transform" - by default, position: fixed is used for pinning only if the scroller is the <body>, otherwise transforms are used (because position: fixed won't work in various nested scenarios), but you can force ScrollTrigger to use position: fixed by setting pinType: "fixed". Typically this isn't necessary or helpful. Beware that if you set the CSS property will-change: transform, browsers treat it just like having a transform applied, breaking position: fixed elements (this is unrelated to ScrollTrigger/GSAP).

 

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

Hi

 

Thank you for your quick answer. The button would only open/close the modal I think it would be adding useless script to make it work.

The .modal is simply opened.

 

I’d rather not change the DOM structure, and anything related to the body or otherwise. My question was simply if it would be possible to have the modal fixed and with the css values intact – not transforming / animating, position relative to the window.

 

 

I tried pin: ".modal" but that seemed worse.

 

I commented the scrollTrigger out so you can see how I want the .modal to appear (I don’t care about the body scrolling for now)

 

Best

 

Link to comment
Share on other sites

99% of the time it is much easier to just restructure the HTML or even modify the CSS to get GSAP the tools that it needs to do its thing. GSAP but certainly ScrollTrigger needs to do a lot of things under the hood to create the best and most performant visual effect for you, so helping it out a bit will make things much easier then the other way around. 

 

What for you is simple might be hard for us, we don't know the full context of what you're working with. In this case I would have created two demos one without the transforms and one with, to show us what GSAP (in this case mainly CSS) does with the fixed element. It took me awhile to figure out, but I see now what your issue is and sadly we can't change the CSS spec, see:

Quote

The CSS Transforms spec explains this behavior. Elements with transforms act as a containing block for fixed position descendants, so position:fixed under something with a transform no longer has fixed behavior.

 

In this case if you're fixed (pun intended) on using the HTML structure as is, I would add the effect to the p element (give it a class otherwise it will also animate the p in the modal).

 

Also a side note I highly recommend getting in to the happy of never translating your trigger element, what you animate and what ScrollTrigger uses as its trigger should never be the same element. 

 

I’ve placed some comments in your JS to better explain things, please be sure to read through them! Hope it helps and happy tweening! 

 

See the Pen PoLQZvZ?editors=0010 by mvaneijgen (@mvaneijgen) on CodePen

  • Like 1
Link to comment
Share on other sites

Hi

 

Thank you for your super fast and thorough answer and solution. Will have to adapt the rest of the DOM to target children.

I wasn’t aware that triggers shouldn’t be animated. 

 

On a side note, you commented that "GSAP is smart and it will just animate to the default". Well, not really in my case, as when I add a second scrollTrigger for when the items leave the page, skewing again in the opposite direction, the transform will not start from the default, but from the skew defined in 'from'. And I don’t think I can fit the whole thing in a timeline, as I need to control precisely the area in witch there is no transform (skew:0).

 

Best 

Link to comment
Share on other sites

Can you create a minimal demo demonstrating the new issue you're having? 

 

When there is just one tween GSAP will animate .from() to an initial value. If you have multiple instances controlling the same element, things get a bit funky and is almost never the best solution. Personally I always like to keep timelines and ScrollTriggers to a minimum and most of the time you can get away with just one by setting things up in a smart way. If for some reason you need multiple ScrollTriggers or timelines controlling the same element you have to do some more logic trouble shooting and either use strict .fromTo() tweens or set immediateRender: false, to later tweens, but this is only something we can assess when we se a minimal demo showing the issue. 

  • Like 1
Link to comment
Share on other sites

Thanks again for your quick answer.

 

Here’s the Pen: 

 

Left column is my implementation of your solution, which works exactly as I need. The right one uses 'from' and 'to' but see the restarting transforms. 

 

 

Link to comment
Share on other sites

  • Solution

Hi,

 

It was just as @mvaneijgen suggested in his last post, using immediateRender: false in the fromTo() instance:

https://gsap.com/docs/v3/GSAP/gsap.fromTo()#special-properties

 

From the Docs:

immediateRender
Normally a tween waits to render for the first time until the very next tick (update cycle) unless you specify a delay. Set immediateRender: true to force it to render immediately upon instantiation. Default: false for to() tweens, true for from() and fromTo() tweens or anything with a scrollTrigger applied.

 

Also this is quite wasteful:

gsap.utils.toArray(".col1").forEach((relements) => {
  const q = gsap.utils.selector(relements);
  ...
});

gsap.utils.toArray(".col1").forEach((relements) => {
  const q = gsap.utils.selector(relements);
  ...
});

Why not run the loop once and be done with it?

 

This seems to work the way you intend:

gsap.utils.toArray(".col1").forEach((relements) => {
  const q = gsap.utils.selector(relements);
  gsap.fromTo(
    q(".toSkew"),
    { skewY: 17, },
    {
      skewY: 0,
      scrollTrigger: {
        trigger: relements,
        scrub: 1,
        start: "top bottom",
        end: "top center",
      }
    }
  );
  gsap.fromTo(
    q(".toSkew"),
    { skewY: 0,},
    {
      skewY: -17,
      immediateRender: false,
      scrollTrigger: {
        trigger: relements,
        scrub: 1,
        start: "top 18%",
        end: "bottom top",
      }
    }
  );
});

gsap.utils.toArray(".col2").forEach((relements) => {
  const q = gsap.utils.selector(relements);
  gsap.from(
    q(".toSkew"),
    { skewY: -17,
      scrollTrigger: {
        trigger: relements,
        scrub: 1,
        start: "top bottom",
        end: "top center"
      }
    }
  );
  gsap.to(
    q(".toSkew"),
    { 
      skewY: 17,
      scrollTrigger: {
        trigger: relements,
        scrub: 1,
        start: "top 18%",
        end: "bottom top",
      immediateRender: false,
      }
    }
  );
});

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

Hi

 

Wow. Thanks for optimizing the code.

 

I didn’t understand why the subsequent trigger was starting from the previous starting transform/state instead of the arrival transform/state (I still don’t) and I would never have guessed on my own that immediateRender would solve it. I guess I still haven’t completely understood the underlying mechanics of GSAP.

 

Anyway, thank you for providing a solution AND optimizing it for me !

 

Best

Link to comment
Share on other sites

6 minutes ago, Nwpn said:

I didn’t understand why the subsequent trigger was starting from the previous starting transform/state instead of the arrival transform/state (I still don’t)

Because GSAP records the starting values on page load, this because it is highly optimised and instead of a tween waiting for its turn it to fire and then start figuring out what it needs to do, it already knows and can just focus on doing that thing blazingly fast. 

 

In your case this isn't what you want, because there are multiple tweens fighting for the control of that element, so you have to help GSAP out a bit. So in this case you tell some of the tweens to be 'lazy' and wait their turn and only start calculating their values when it is time to do so. 

 

Hope that clears things up and happy tweening!

  • Like 1
Link to comment
Share on other sites

OK so basically, GSAP will calculate the starting and ending values for the first tween and then do it for the second one, which inherits the values of the first one. But is it really much more efficient to delay calculation with immediateRender:false than to have two fromTo tweens ?

Link to comment
Share on other sites

On 2/1/2024 at 6:35 AM, Nwpn said:

OK so basically, GSAP will calculate the starting and ending values for the first tween and then do it for the second one, which inherits the values of the first one.

Actually the second instance doesn't inherits anything from any previous instances. GSAP simply takes the element/object you pass as a target, checks the properties you want to tween and then gets the current values for those properties stores it and when the Tween/Timeline instance runs simply iterates between those values.

 

The challenge in the scenario you have is that there are two consecutive from/fromTo instances in the same element, so GSAP will record the value you want to tween from and the current value and then proceed to apply the value you passed in the from configuration, so when the Tween runs the values are updated. But before that happens (the Tween running and animating the element) another from/fromTo instance is executed, so GSAP does the same, takes the current value of the properties and then applies the values in the from configuration. The problem is that the second instance takes the initial value applied by the first from instance (in your case minus 17 degrees) and assumes that that is the natural value. By setting immediateRender as false you're telling GSAP, record the initial value when you first run and not when you first render in order to avoid the visual glitch. Consecutive from and fromTo instances are tricky to grasp at first, but once you've worked enough with GSAP you'll get the hang of them. As Mitchel mentions, there are always two or more ways to achieve the same result, is just a matter of how creative you'll get with your code.

 

Hopefully this helps

Happy Tweening!

Link to comment
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...