<div id="root" class="panel center text-center"></div>
.app {
  padding: 10px;
}

.box {
  margin: 10px auto;
  will-change: transform;
}
import React, {
  useEffect,
  useLayoutEffect,
  useRef,
  forwardRef,
  useState
} from "https://esm.sh/react@18.3.1";
import ReactDOM from "https://esm.sh/react-dom@18.3.1";

import gsap from "https://esm.sh/gsap-trial";
import { CustomEase } from "https://esm.sh/gsap-trial/CustomEase";
import { CustomWiggle } from "https://esm.sh/gsap-trial/CustomWiggle";
import { useGSAP } from "https://esm.sh/@gsap/react?deps=react@18.3.1";

console.clear();

gsap.registerPlugin(CustomEase, CustomWiggle);
gsap.config({ trialWarn: false });

CustomWiggle.create("myWiggle", {
  wiggles: 8,
  type: "uniform"
});

gsap.registerEffect({
  name: "pulse",
  effect(targets) {
    return gsap.fromTo(
      targets,
      {
        scale: 1
      },
      {
        scale: 1.5,
        repeat: 1,
        ease: "bounce",
        yoyoEase: "power3"
      }
    );
  }
});

gsap.registerEffect({
  name: "spin",
  effect(targets) {
    return gsap.to(targets, {
      rotation: (i, el) =>
        gsap.utils.snap(360, gsap.getProperty(el, "rotation") + 360)
    });
  }
});

gsap.registerEffect({
  name: "shake",
  effect(targets) {
    return gsap.fromTo(
      targets,
      {
        x: 0
      },
      {
        x: 10,
        ease: "myWiggle"
      }
    );
  }
});

const GsapEffect = forwardRef(({ children, effect, targetRef, vars }, ref) => {
  const animation = useRef();

  useGSAP(() => {
    if (gsap.effects[effect]) {
      const t = gsap.effects[effect](targetRef.current, vars);
      animation.current = t;
    }
  }, [effect, targetRef, vars]);

  useGSAP(() => {
    // forward the animation instance if a ref is passed
    if (typeof ref === "function") {
      ref(animation.current);
    } else if (ref) {
      ref.current = animation.current;
    }
  }, [ref]);

  return <>{children}</>;
});

const Box = forwardRef(({ children }, ref) => {
  return (
    <div className="box gradient-blue" ref={ref}>
      {children}
    </div>
  );
});

const wrap = gsap.utils.wrap(["pulse", "spin", "shake"]);

function App() {
  const boxRef = useRef();
  const count = useRef(0);
  const [effect, setEffect] = useState("");

  const toggle = () => {
    setEffect(wrap(count.current++));
  };

  return (
    <div className="app">
      <div>
        <button onClick={toggle}>Toggle</button>
      </div>
      <p>Effect: {effect}</p>
      <GsapEffect targetRef={boxRef} effect={effect}>
        <Box ref={boxRef}>Box</Box>
      </GsapEffect>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
View Compiled

External CSS

  1. https://codepen.io/GreenSock/pen/xxmzBrw.css

External JavaScript

  1. https://assets.codepen.io/16327/CustomEase3.min.js
  2. https://assets.codepen.io/16327/CustomWiggle3.min.js
  3. https://codepen.io/GreenSock/pen/NWoLXRG.js