<svg fill="none" viewBox="0 0 200 200" style="opacity: 0;">
  <g>
    <g filter="url(#filter0_f)">
      <ellipse cx="100.5" cy="140.5" fill="#F84F8A" rx="60.5" ry="23.5" />
    </g>
    <path stroke="#3B0216" fill="url(#gradient)" stroke-width="2" d="M112.411 54.44l8.465 54.001C121.9 114.975 116.388 121 108.965 121h-16.93c-7.423 0-12.935-6.025-11.91-12.559l8.464-54C89.567 48.198 94.939 45 100.5 45c5.562 0 10.932 3.198 11.911 9.44z" />
    <path stroke="#3B0216" fill="url(#gradient)" stroke-width="2" d="M109.788 58.444c1-6.23 6.494-9.444 12.212-9.444s11.212 3.214 12.212 9.444l8.661 54c1.044 6.508-4.576 12.556-12.211 12.556h-17.324c-7.635 0-13.255-6.048-12.211-12.556l8.661-54z" />
    <path stroke="#3B0216" fill="url(#gradient)" stroke-width="2" d="M66.788 58.444C67.788 52.214 73.282 49 79 49s11.212 3.214 12.212 9.444l8.661 54C100.917 118.952 95.297 125 87.662 125H70.338c-7.635 0-13.255-6.048-12.211-12.556l8.661-54z" />
    <path stroke="#3B0216" fill="url(#gradient)" stroke-width="2" d="M129 93c0 16.016-12.984 29-29 29s-29-12.984-29-29 12.984-29 29-29 29 12.984 29 29z" />

    <path stroke="#3B0216" fill="url(#gradient)" stroke-width="2" d="M49.788 67.444C50.788 61.214 56.282 58 62 58s11.212 3.214 12.212 9.444l8.661 54C83.917 127.952 78.297 134 70.662 134H53.338c-7.635 0-13.255-6.048-12.211-12.556l8.661-54z" />

    <path stroke="#3B0216" fill="url(#gradient)" stroke-width="2" d="M65.788 78.444C66.788 72.214 72.282 69 78 69s11.212 3.214 12.212 9.444l8.661 54C99.917 138.952 94.297 145 86.662 145H69.338c-7.635 0-13.255-6.048-12.211-12.556l8.661-54z" />

    <path stroke="#3B0216" fill="url(#gradient)" stroke-width="2" d="M126.788 67.444c1-6.23 6.494-9.444 12.212-9.444s11.212 3.214 12.212 9.444l8.661 54c1.044 6.508-4.576 12.556-12.211 12.556h-17.324c-7.635 0-13.255-6.048-12.211-12.556l8.661-54z" />

    <path stroke="#3B0216" fill="url(#gradient)" stroke-width="2" d="M110.788 78.444c1-6.23 6.494-9.444 12.212-9.444s11.212 3.214 12.212 9.444l8.661 54c1.044 6.508-4.576 12.556-12.211 12.556h-17.324c-7.635 0-13.255-6.048-12.211-12.556l8.661-54z" />

    <path stroke="#3B0216" fill="url(#gradient)" stroke-width="2" d="M88.589 88.44c.978-6.242 6.35-9.44 11.911-9.44 5.562 0 10.932  3.198 11.911 9.44l8.465 54.001C121.9 148.975 116.388 155 108.965 155h-16.93c-7.423 0-12.935-6.025-11.91-12.559l8.464-54z" />
    <g id="eye-left">
      <circle cx="88" cy="97" r="7" fill="#FDD9E4" stroke="#3B0216" stroke-width="2" />
      <circle id="eye-left-inner" cx="91" cy="97" r="4" fill="#3B0216" />
    </g>
    <g id="eye-right">
      <circle cx="113" cy="97" r="7" fill="#FDD9E4" stroke="#3B0216" stroke-width="2" />
      <circle id="eye-right-inner" cx="116" cy="97" r="4" fill="#3B0216" />
    </g>
    <path stroke="#3B0216" stroke-linecap="round" stroke-width="2" d="M95 114h11" />

    <g id="mouth-open" transform="translate(0 2)">
      <path d="M94 113C94 111.895 94.8954 111 96 111H105C106.105 111 107 111.895 107 113V114C107 117.314 104.314 120 101 120H100C96.6863 120 94 117.314 94 114V113Z" fill="#3B0216" />
      <mask id="mask0" maskUnits="userSpaceOnUse" x="94" y="111" width="13" height="9">
        <path d="M94 113C94 111.895 94.8954 111 96 111H105C106.105 111 107 111.895 107 113V114C107 117.314 104.314 120 101 120H100C96.6863 120 94 117.314 94 114V113Z" fill="white" />
      </mask>
      <g mask="url(#mask0)">
        <ellipse cx="97.5" cy="130.562" rx="13.5" ry="15.1875" fill="#FE5D96  " />
      </g>
    </g>
  </g>

  <defs>
    <linearGradient id="gradient" gradientTransform="rotate(90)">
      <stop offset="5%" stop-color="#F84F8A" />
      <stop offset="75%" stop-color="#F50A5D" />
    </linearGradient>
  </defs>
</svg>

<p class="hover">Hover me!</p>
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700&display=swap');

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  position; relative;
  height: 100vh;
  display: grid;
  place-items: center;
  background: radial-gradient(
    circle at center,
    hsl(339, 92%, 99%),
    hsl(339, 92%, 64%)
  );
}

svg {
  width: 90vmin;
  height: 90vmin;
  max-width: 640px;
}

.stars {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  opacity: 0;
}

.stars span {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}


.hover {
  position: absolute;
  bottom: 1rem;
  right: 1rem;
  font-size: 20px;
  font-weight: 700;
  color: #3B0216;
  font-family: 'Nunito', system-ui, sans-serif;
}
import {
  spline,
  pointsInPath,
  createCoordsTransformer
} from "https://cdn.skypack.dev/@georgedoescode/generative-utils@1.0.1";
import { Vector2D } from "https://cdn.skypack.dev/@georgedoescode/vector2d@1.0.1";
import gsap from "https://cdn.skypack.dev/gsap@3.6.1";
import debounce from "https://cdn.skypack.dev/debounce@1.2.1";

console.clear();

let isWobbling = false;

function createLiquidPath(path, opts) {
  const svgPoints = pointsInPath(path, opts.detail);
  let originPoints = svgPoints.map((p) => new Vector2D(p.x, p.y));
  let liquidPoints = originPoints.map((p) => new Vector2D(p.x, p.y));

  if (
    Vector2D.dist(liquidPoints[liquidPoints.length - 1], liquidPoints[0]) < 8
  ) {
    liquidPoints = liquidPoints.slice(0, liquidPoints.length - 1);
  }

  const transformCoords = createCoordsTransformer(path.closest("svg"));
  const mousePos = new Vector2D(0, 0);

  const maxDist = {
    x: opts.axis.includes("x")
      ? Vector2D.dist(originPoints[0], originPoints[1]) / 2
      : 0,
    y: opts.axis.includes("y")
      ? Vector2D.dist(originPoints[0], originPoints[1]) / 2
      : 0
  };

  window.addEventListener("mousemove", (e) => {
    const { x, y } = transformCoords(e);

    mousePos.x = x;
    mousePos.y = y;

    // use gsap to lerp vertex { x, y } values on mousemove
    liquidPoints.forEach((point, index) => {
      const pointOrigin = originPoints[index];
      const distX = Math.abs(pointOrigin.x - mousePos.x);
      const distY = Math.abs(pointOrigin.y - mousePos.y);

      if (distX < opts.range.x && distY < opts.range.y) {
        isWobbling = true;

        const d = Vector2D.sub(pointOrigin, mousePos);
        const target = Vector2D.sub(pointOrigin, new Vector2D(-d.x, -d.y));

        const x = gsap.utils.clamp(
          pointOrigin.x - maxDist.x,
          pointOrigin.x + maxDist.x,
          target.x
        );
        const y = gsap.utils.clamp(
          pointOrigin.y - maxDist.y,
          pointOrigin.y + maxDist.y,
          target.y
        );

        gsap.to(point, {
          x: x,
          y: y,
          ease: "sine",
          overwrite: true,
          duration: 0.175,
          onComplete() {
            gsap.to(point, {
              x: pointOrigin.x,
              y: pointOrigin.y,
              ease: "elastic.out(1, 0.3)",
              duration: 1.25
            });
          }
        });
      }
    });
  });

  gsap.ticker.add(() => {
    gsap.set(path, {
      attr: {
        d: spline(liquidPoints, opts.tension, opts.close)
      }
    });

    if (isWobbling) {
      gsap.to("#eye-right-inner", {
        attr: {
          cx: 116
        }
      });

      gsap.to("#eye-left-inner", {
        attr: {
          cx: 91
        }
      });
      gsap.set("#mouth-open", {
        scaleY: 1,
        transformOrigin: "50% 0%",
        duration: 0.3
      });
    } else {
      gsap.to("#eye-right-inner", {
        attr: {
          cx: 113
        }
      });

      gsap.to("#eye-left-inner", {
        attr: {
          cx: 88
        }
      });

      gsap.set("#mouth-open", {
        scaleY: 0,
        transformOrigin: "50% 0%",
        duration: 0.3
      });
    }
  });
}

Array.from(document.querySelectorAll("path"))
  .slice(0, 9)
  .forEach((path) => {
    createLiquidPath(path, {
      detail: 20,
      tension: 1,
      close: true,
      range: {
        x: 12,
        y: 12
      },
      axis: ["x", "y"]
    });
  });

const eyeLeft = document.getElementById("eye-left");
const eyeRight = document.getElementById("eye-right");

window.addEventListener("mousemove", (e) => {
  const { clientX, clientY } = e;

  const heading = new Vector2D(
    clientX - window.innerWidth / 2,
    clientY - window.innerHeight / 2
  ).heading();

  let deg = heading * 57.2958;

  gsap.to([eyeLeft, eyeRight], {
    rotate: deg + "_short",
    transformOrigin: "50% 50%",
    ease: "elastic.out(1, 0.4)",
    duration: 1.5,
    stagger: 0.05
  });
});

window.addEventListener(
  "mousemove",
  debounce(() => {
    isWobbling = false;
  }, 750)
);

gsap.set("svg", {
  opacity: 1
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.