Jump to content
Search Community

Morph onClick and then Morph back onClick React

blumaa@gmail.com test
Moderator Tag

Recommended Posts

Hi! My question is more for feedback purposes. I am creating a hamburger menu morph. Is there a better way to do this other than to create two different scenes on the timeline (the hamburger morphs into the x in menuMorph(), then the x morphs back into the hamburger in menuMorphReverse())?

import React, { useRef, useEffect, useState } from "react";
import { gsap, Power3 } from "gsap";
import { MorphSVGPlugin } from "gsap/MorphSVGPlugin";

const MenuMorph = () => {
  const [show, setShow] = useState(false);

  let topLine = useRef(null);
  let middleLine = useRef(null);
  let bottomLine = useRef(null);

  let closeCircle = useRef(null);
  let closeLine2 = useRef(null);
  let closeLine1 = useRef(null);

  let master = gsap.timeline({
      repeat: 0,
    paused: true,
  });

  
  const menuMorph = () => {
      
      let tl = gsap.timeline();
      tl.addLabel('start').to(topLine.current, 1, {
          morphSVG: { shape: closeCircle.current, shapeIndex: 7 },
          ease: Power3.easeOut,
        }, 'start').to(middleLine.current, 1, {
            morphSVG: { shape: closeLine2.current, shapeIndex: 1 },
            ease: Power3.easeOut,
        }, 'start').to(bottomLine.current, 1, {
            morphSVG: { shape: closeLine1.current, shapeIndex: 1 },
            ease: Power3.easeOut,
        }, 'start');
        
        return tl
    }

  const menuMorphReverse = () => {
      
      let tl = gsap.timeline();
      tl.addLabel('start').to(topLine.current, 1, {
          morphSVG: { shape: topLine.current, shapeIndex: 7 },
          ease: Power3.easeOut,
        }, 'start').to(middleLine.current, 1, {
            morphSVG: { shape: middleLine.current, shapeIndex: 1 },
            ease: Power3.easeOut,
        }, 'start').to(bottomLine.current, 1, {
            morphSVG: { shape: bottomLine.current, shapeIndex: 1 },
            ease: Power3.easeOut,
        }, 'start');
        
        return tl
    }

    show ? master.add(menuMorph()).play() : master.add(menuMorphReverse()).play()

    
    const handleClick = () => {
        setShow((prevState) => {
            // console.log(prevState)
            return prevState ? false : true;
        });
    };

  return (
    <svg
      id="menu_morph"
      data-name="Layer 1"
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 480 480"
      onClick={handleClick}
    >
      <title>menuMorph</title>
      <g id="menu">
        <g id="Menu-2" data-name="Menu">
          <path
            id="topline"
            d="M382.05,1109.73H743a12,12,0,0,0,0-24.06H382.05a12,12,0,0,0,0,24.06Z"
            transform="translate(-326 -974)"
            ref={topLine}
          />
          <path
            id="middleline"
            d="M743,1206H382.05a12,12,0,0,0,0,24.06H743a12,12,0,1,0,0-24.06Z"
            transform="translate(-326 -974)"
            ref={middleLine}
          />
          <path
            id="bottomline"
            d="M743,1326.27H382.05a12,12,0,0,0,0,24.06H743a12,12,0,0,0,0-24.06Z"
            transform="translate(-326 -974)"
            ref={bottomLine}
          />
        </g>
      </g>
      <g id="close">
        <path
          id="closecirlce"
          d="M562.5,1029.5c-103.94,0-188.5,84.56-188.5,188.5s84.56,188.5,188.5,188.5a187.61,187.61,0,0,0,130.63-52.6,7.67,7.67,0,1,0-10.64-11.06,172.31,172.31,0,0,1-120,48.32c-95.48,0-173.16-77.68-173.16-173.16S467,1044.84,562.5,1044.84,735.66,1122.52,735.66,1218a172.52,172.52,0,0,1-11.6,62.44,7.67,7.67,0,1,0,14.31,5.54A187.72,187.72,0,0,0,751,1218C751,1114.06,666.44,1029.5,562.5,1029.5Z"
          transform="translate(-326 -974)"
          ref={closeCircle}
        />
        <path
          id="closeline2"
          d="M648.78,1131.72a7.67,7.67,0,0,0-10.85,0L476.22,1293.43a7.67,7.67,0,1,0,10.85,10.85l161.71-161.71A7.67,7.67,0,0,0,648.78,1131.72Z"
          transform="translate(-326 -974)"
          ref={closeLine2}
        />
        <path
          id="closeline1"
          d="M476.22,1131.72a7.67,7.67,0,0,0,0,10.85l161.71,161.71a7.67,7.67,0,1,0,10.85-10.85L487.07,1131.72A7.67,7.67,0,0,0,476.22,1131.72Z"
          transform="translate(-326 -974)"
          ref={closeLine1}
        />
      </g>
    </svg>
  );
};

export default MenuMorph;

 

Link to comment
Share on other sites

2 hours ago, blumaa@gmail.com said:

Is there a better way to do this other than to create two different scenes on the timeline (the hamburger morphs into the x in menuMorph(), then the x morphs back into the hamburger in menuMorphReverse())?

Yes, absolutely. The best way is to create the timeline fully beforehand and just use methods to control its playback inside of the event callbacks. For example in menuMorph you could just say hamburgerTL.play() or whatever you call the timeline. I write about how to set things up in this way and a bunch of other useful tips in my article on animating efficiently.

  • Like 2
Link to comment
Share on other sites

Your article is extremely insightful. Thank you for that! However, when I try to implement a simple play/reverse feature for my animation, I'm not getting it to work. Does this have something to do with how React handle the play/reverse features in Gsap?


Thanks!

import React, { useRef, useEffect, useState } from "react";
import { gsap, Power3 } from "gsap";
import { MorphSVGPlugin } from "gsap/MorphSVGPlugin";

const MenuMorph = () => {
  const [show, setShow] = useState(false);

  let topLine = useRef(null);
  let middleLine = useRef(null);
  let bottomLine = useRef(null);

  let closeCircle = useRef(null);
  let closeLine2 = useRef(null);
  let closeLine1 = useRef(null);


  let tl = gsap.timeline({paused: true}).to(topLine.current, 1, {
    morphSVG: { shape: closeCircle.current, shapeIndex: 22 },
    ease: Power3.easeOut,
  })
    .to(middleLine.current, 1, {
      morphSVG: { shape: closeLine2.current, shapeIndex: 1 },
      ease: Power3.easeOut,
    })
    .to(bottomLine.current, 1, {
      morphSVG: { shape: closeLine1.current, shapeIndex: 1 },
      ease: Power3.easeOut,
    });

  const handlePlay = () => {
    tl.play()
  };
  const handleReverse = () => {
    tl.reverse()
  };

  return (
    <>
      <div class="nav">
        <button id="play" onClick={()=>handlePlay()}>
          play()
        </button>
        <button id="pause">pause()</button>
        <button id="resume">resume()</button>
        <button id="reverse" onClick={()=>handleReverse()}>
          reverse()
        </button>
      </div>
      <svg
        id="menu_morph"
        data-name="Layer 1"
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 480 480"
      >
        <title>menuMorph</title>
        <g id="menu">
          <g id="Menu-2" data-name="Menu">
            <path
              id="topline"
              d="M382.05,1109.73H743a12,12,0,0,0,0-24.06H382.05a12,12,0,0,0,0,24.06Z"
              transform="translate(-326 -974)"
              ref={topLine}
            />
            <path
              id="middleline"
              d="M743,1206H382.05a12,12,0,0,0,0,24.06H743a12,12,0,1,0,0-24.06Z"
              transform="translate(-326 -974)"
              ref={middleLine}
            />
            <path
              id="bottomline"
              d="M743,1326.27H382.05a12,12,0,0,0,0,24.06H743a12,12,0,0,0,0-24.06Z"
              transform="translate(-326 -974)"
              ref={bottomLine}
            />
          </g>
        </g>
        <g id="close">
          <path
            id="closecirlce"
            d="M562.5,1029.5c-103.94,0-188.5,84.56-188.5,188.5s84.56,188.5,188.5,188.5a187.61,187.61,0,0,0,130.63-52.6,7.67,7.67,0,1,0-10.64-11.06,172.31,172.31,0,0,1-120,48.32c-95.48,0-173.16-77.68-173.16-173.16S467,1044.84,562.5,1044.84,735.66,1122.52,735.66,1218a172.52,172.52,0,0,1-11.6,62.44,7.67,7.67,0,1,0,14.31,5.54A187.72,187.72,0,0,0,751,1218C751,1114.06,666.44,1029.5,562.5,1029.5Z"
            transform="translate(-326 -974)"
            ref={closeCircle}
          />
          <path
            id="closeline2"
            d="M648.78,1131.72a7.67,7.67,0,0,0-10.85,0L476.22,1293.43a7.67,7.67,0,1,0,10.85,10.85l161.71-161.71A7.67,7.67,0,0,0,648.78,1131.72Z"
            transform="translate(-326 -974)"
            ref={closeLine2}
          />
          <path
            id="closeline1"
            d="M476.22,1131.72a7.67,7.67,0,0,0,0,10.85l161.71,161.71a7.67,7.67,0,1,0,10.85-10.85L487.07,1131.72A7.67,7.67,0,0,0,476.22,1131.72Z"
            transform="translate(-326 -974)"
            ref={closeLine1}
          />
        </g>
      </svg>
    </>
  );
};

export default MenuMorph;

 

Link to comment
Share on other sites

There really needs to be some official documentation on React Hooks, because I see everyone doing it wrong.

 

When using Hooks, you should define your animations inside the first useEffect.

 

// GOOD
const MyComponent = () => {

  let timeline = useRef(null);
  
  useEffect(() => {
  
    timeline.current = gsap.timeline();
    
    // Return function to kill animation on unmount
    return () => {
      timeline.current.kill();
    };
    
  }, []); // !!! NOTICE THE EMPTY ARRAY
  
  return (<div>Foo</div>);
};

 

NEVER set it as variable.

 

// BAD
const MyComponent = () => {

  let timeline = gsap.timeline();
  
  return (<div>Foo</div>);
};

 

  • Like 2
Link to comment
Share on other sites

1 minute ago, ZachSaucier said:

And is "official" the React way or the way that we should recommend doing it as GSAP people?

 

Both.

 

1 minute ago, ZachSaucier said:

Thoughts on where?

 

Not sure. Maybe create a usage section somewhere that shows how to use gsap with different frameworks and libraries (React, React Hooks, Vue, Angular, canvas, Pixi, Three, etc).

 

 

  • Like 1
Link to comment
Share on other sites

14 minutes ago, ZachSaucier said:

Would you or @Rodrigo be interested in writing an article on using React Hooks with GSAP? Like our https://greensock.com/react page.

 

Hm... how do you find that page.

 

But I'm not going to have enough free time to do that anytime soon, but it def needs to be updated to use React Hooks. My prediction is that 99% of all new React questions will be about React Hooks.

 

Link to comment
Share on other sites

57 minutes ago, ZachSaucier said:

Would you or @Rodrigo be interested in writing an article on using React Hooks with GSAP? Like our https://greensock.com/react page.

As I mentioned months ago I don't have enough time now to update that article or write a new one right now. Perhaps in a couple of months I'll have time to dedicate myself to it.

 

47 minutes ago, ZachSaucier said:

And is "official" the React way or the way that we should recommend doing it as GSAP people?

Well right now the "official" React way is to use hooks, which is something Blake and I have ranted about a few times. The best approach GSAP-wise is to use class components and life-cycle methods, but it feels like is just Blake and Me against the entire React community on this one

giphy.gif

  • Like 3
  • Haha 1
Link to comment
Share on other sites

2 hours ago, Rodrigo said:

Well right now the "official" React way is to use hooks, which is something Blake and I have ranted about a few times. The best approach GSAP-wise is to use class components and life-cycle methods, but it feels like is just Blake and Me against the entire React community on this one

 

My official recommendation is to use Vue. 

  • Like 1
  • Haha 3
Link to comment
Share on other sites

32 minutes ago, blumaa@gmail.com said:

Also.. still having trouble setting up MorphSVGPlugin via Codesandbox using the script tag. I think I'm doing it right but idk. Got any example for me? @OSUblake

 

That's strange. I see the problem, but that's something @GreenSock will have to fix for the next release. Try using stackblitz instead.

 

@GreenSock When creating a React project on CodeSandox, it uses a different url than other projects. I think you need to whitelist the csb.app domain.

 

The url for a Vue project

 

image.png.8c23729f15bdc76350a99d2042b39f80.png

 

 

The url for a React project 🤷‍♂️

 

image.png.0f604f9fc0266732a9ac215c8900f45c.png

 

 

 

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