Jump to content
Search Community

Flipping image with scrollTrigger three or multiple times in parent sections...

ashura test
Moderator Tag

Recommended Posts

Hi, I'm a bit troubling how can I Flip the image to another parent element. 
 

Like let say I have three parents "section1" , "section2", "section3". 
Now once I am scrolling down with the scrollTrigger pin main parent which is their container. I want the image from section1 bounce each to section2 to section 3. 

As you see this codes only relies on one item...but I can't possibly make it work with transfering with another parent. Although it is easy with using click buttons but I wanna do it in just relying with scrollTrigger.  Anyway here is the link...
https://codesandbox.io/s/laughing-yalow-xfrzmg?file=/src/FlipScrolls.jsx

 

And this are the main functionality of codes...
 

  const p1 = useRef(null);
  const p2 = useRef(null);
  const p3 = useRef(null);
  const bg = useRef(null);
  let flipCtx2;
  const ctx = useRef(gsap.context(() => {}));

  useEffect(() => {
    ctx.current.add(() => {
      flipCtx2 && flipCtx2.revert();

      p1.current.appendChild(bg.current);
      // p3.current.appendChild(bg.current)
      const state = Flip.getState(bg.current);
      p2.current.appendChild(bg.current);
      let flip = Flip.from(state, { scale: true, absolute: true });

      // p3.current.appendChild(bg.current)

      ScrollTrigger.create({
        trigger: ".container",
        endTrigger: p3.current,
        start: "clamp(+=10 center)",
        scrub: true,
        pin: true,
        end: "+=300%",
        animation: flip
      });
    });
  }, []);

 

Link to comment
Share on other sites

Hi,

 

There are a few issues in your code:

  1. You are creating a GSAP Context in a ref which is not being reverted on cleanup:
    const ctx = useRef(gsap.context(() => {}));
    
    useEffect(() => {
      ctx.current.add(() => {
        flipCtx2 && flipCtx2.revert();
    
        p1.current.appendChild(bg.current);
        // p3.current.appendChild(bg.current)
        const state = Flip.getState(bg.current);
        p2.current.appendChild(bg.current);
        let flip = Flip.from(state, { scale: true, absolute: true });
    
        // p3.current.appendChild(bg.current)
    
        ScrollTrigger.create({
          trigger: ".container",
          endTrigger: p3.current,
          start: "clamp(+=10 center)",
          scrub: true,
          pin: true,
          end: "+=300%",
          animation: flip
        });
      });
    }, []);

    You have to cleanup that context (ctx.current) in order to avoid other issues (it seems that the source of your problem is not tied to this though):
     

    const ctx = useRef(gsap.context(() => {}));
    
    useEffect(() => {
      ctx.current.add(() => {
        flipCtx2 && flipCtx2.revert();
    
        p1.current.appendChild(bg.current);
        // p3.current.appendChild(bg.current)
        const state = Flip.getState(bg.current);
        p2.current.appendChild(bg.current);
        let flip = Flip.from(state, { scale: true, absolute: true });
    
        // p3.current.appendChild(bg.current)
    
        ScrollTrigger.create({/*...*/});
      });
      return () => ctx.current.revert();
    }, []);
  2. This is most definitely not the way to update the DOM in React, because when you do this:
    p1.current.appendChild(bg.current);
    // p3.current.appendChild(bg.current)
    const state = Flip.getState(bg.current);
    p2.current.appendChild(bg.current);

    React has no idea that you modified the DOM, so if your component re-renders you'll get the previous version of the DOM, not the one you're expecting and that will lead to unexpected behaviour. Always use state properties to update the DOM and reflect those changes in the next render of your component/app.

  3. Using Flip with React (and any other framework that uses some sort of shadow DOM) comes with a bit more setup than in Vanilla JS environments. In this case because of React's reconciliation algorithm.

Right now I don't have time to dig into this and create a working example of this usage. But the steps should like this:

  1. Use state in and conditional rendering in order to reparent the element, not DOM manipulation (honestly some React folks could have a stroke or condemn you to the fire pits if they see all those appendChild() calls there :D).
  2. Before updating the state to reparent the element store the current element state in a useRef() instance using Flip.getState():
    const flipState = useRef();
    // In this case I'd use index and create an array with the parents.
    // Default to the first parent element, thus index zero
    const [currentParent, setCurrentParent] = useState(0);
    
    const toggleParent = (index) => {
      // Create some code to reparent the element using ScrollTrigger
      flipState.current = Flip.getState(/* Get State Stuff Here */);
      setCurrentParent(index);
    };
    
    // After reparenting the element and the DOM is updated
    // Create the Flip Animation
    useLayoutEffect(() => {
      Flip.from(flipState.current);
    }, [currentParent]);
  3. Is worth noticing that using scrub in ScrollTrigger in this case brings a new set of complexities to the case. If I was you I'd start by creating some buttons to just reparent the element and make sure that the Flip animations work as expected. Then use ScrollTrigger's toggle actions to replace the buttons' code. Finally try to tie the Flip animations to ScrollTrigger with scrub.

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

7 hours ago, Rodrigo said:
  • Use state in and conditional rendering in order to reparent the element, not DOM manipulation (honestly some React folks could have a stroke or condemn you to the fire pits if they see all those appendChild() calls there :D).

Hi, thanks for the response Rodrigo, but I'm not sure how to do that? I'm sorry I'm a bit lost but I don't understand it well. When you mean reparent with conditional how does that mean? I'm sorry but I can't imagine the code of how can I reparent it without using DOM manipulation... Please can I get a sample code or anything... 

 

Link to comment
Share on other sites

Hi,

 

Sorry I don't have time today at least to create an example 😞

 

What I mean is conditional rendering in order to put the element in a parent or another based on some condition:

return (
  <>
    <div id="parentOne">
      { someCondition && <ChildElement /> }
    </div>
    <div id="parentTwo">
      { someCondition && <ChildElement /> }
    </div>
    <div id="parentThree">
      { someCondition && <ChildElement /> }
    </div>
  </>
);

Of course in this case could be a function that returns the HTML for the child element, it doesn't have to be a component. But that's basically the gist of it, change someCondition based on click or some method that you can call with ScrollTrigger's callbacks (onEnter, onLeave, etc.) and then conditionally render the child.

 

If I was you I'd try this without GSAP first. Just use some buttons to conditionally render the child element depending on a state property. Then add GSAP and Flip and when you have all of that working, add ScrollTrigger.

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

Ok I follow the procedure you give to me sir @Rodrigo, but the problem is that no animation of Flip happening here.  This is the functionality. What I mean no Flip is that there is no jumping the '.p-bg' happening from to another. Where part I am wrong? It is working though but the jumping one to another is not. 

    const p1 = useRef(null);
  const p2 = useRef(null);
  const p3 = useRef(null);
  const itemsRef = useRef([]);
  const bg = useRef(null);
  const [openState, setOpenState] = useState(false);
  const ctx = useRef(gsap.context(() => {}));
  const [currentParent, setCurrentParent] = useState(0);
  const flipState = useRef();

  // useEffect(() => {
  //     ctx.current.add(() => {

  //     })

  //     return () => {
  //         ctx.current.revert()
  //     }

  // }, []);

  const handleNextItem = () => {
    if (currentParent == 2) {
      setCurrentParent(0);
      flipState.current = Flip.getState(bg.current, {
        props: "transform, top, left"
      });
    } else {
      setCurrentParent(currentParent + 1);
      flipState.current = Flip.getState(bg.current, {
        props: "transform, top, left"
      });
    }
  };

  const ChildElement = () => (
    <div className="p-bg" ref={bg}>
      <img src="./products/OshiB.jpg" alt="" />
    </div>
  );

  useLayoutEffect(() => {
    if (flipState.current) {
      Flip.from(flipState.current, {
        targets: ".p-bg",
        absolute: true,
        scale: true,
        ease: "none",
        duration: 1.5,
        onEnter: (elements) => {
          return gsap.fromTo(
            elements,
            {
              opacity: 0,
              scale: 0
            },
            {
              opacity: 1,
              scale: 1,
              delay: 0.2,
              duration: 0.3
            }
          );
        },
        onLeave: (elements) => {
          return gsap.to(elements, {
            opacity: 0,
            scale: 0
          });
        }
      });
    }
  }, [currentParent]);

And This is the Display. 

    <div className="container">
      <div className="active-button" onClick={(e) => handleNextItem()}>
        Next Item
      </div>
      <div className="p p-1 active" ref={(e) => itemsRef.current.push(e)}>
        {currentParent == 0 && ChildElement()}
        {/* {ChildElement()}  */}
        <p>
          Lorem Ipsum is simply dummy text of the printing and typesetting
          industry.
        </p>
      </div>
      <div className="p rev p-2 p-right" ref={(e) => itemsRef.current.push(e)}>
        {currentParent == 1 && ChildElement()}
        {/* {ChildElement()}  */}
        <p>My Life Has Been Bullish</p>
      </div>
      <div className="p rev p-2 p-right" ref={(e) => itemsRef.current.push(e)}>
        {currentParent == 2 && ChildElement()}
        {/* {ChildElement()}  */}
        <p> My Life Has Been Bearish </p>
      </div>
    </div>

The codes are modified in the same link I give but it seems no Flip animating happening as you mention it. What did I miss? 
https://codesandbox.io/s/laughing-yalow-xfrzmg?file=/src/FlipScrolls.jsx

Link to comment
Share on other sites

Hi,

 

If the idea is to scale down and hide the element before reparenting it, I don't think you need the Flip Plugin for this. Just create a method that does this:

gsap.to(elements, {
  opacity: 0,
  scale: 0,
  // When this animation completes, re-parent the element
  onComplete: handleNextItem,
});

Then just use the useLayoutEffect to run this when the element has been rendered in the new parent element:

useLayoutEffect(() => {
  const ctx = gsap.context(() => {
    gsap.fromTo(
      element,
      {
        opacity: 0,
        scale: 0
      },
      {
        opacity: 1,
        scale: 1,
        delay: 0.2,
        duration: 0.3
      }
    );
  });
  return () => ctx.revert();
}, [currentParent]);

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

No, it supposed to Flip..I just use a simple Flip by using scale and opacity just to make it work but it doesn't apply the Flip animation @Rodrigo jumping the item to another one parent. This is not what I meant my bad. It is just a demo of how I think the flip will happen but I just used a scale and opacity. 

Link to comment
Share on other sites

Hi,

 

OK, so in the future is better to have your demos to look exactly like what you're trying to do since it was a bit misleading.

 

So the issue here is related to the way these type of frameworks (React, Vue, Svelte, etc) work. So when you reparent the element what React is actually doing is recreating it again, even if is not noticeable. So the Flip plugin gets the state of the element and that's stored, then you re-parent it and after that you tell Flip animate from that state. So Flip looks at the state and sees that the element is not the same (even though the HTML structure actually is) so there is no animation. For that you can use data-flip-id in the child element and the targets property in order to tell Flip that regardless of the outlook of the HTML, it should animate that particular target from the state you're passing which will have of course the same data-flip-id attribute.

 

I created a simple example of Flip reparenting here:

https://stackblitz.com/edit/vitejs-vite-bpwfpk?file=src%2FApp.jsx

 

That should be enough to get you going with the rest of your ScrollTrigger setup.

 

Hopefully this helps.

Happy Tweening!

  • Thanks 1
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...