Skip to content

Instantly share code, notes, and snippets.

@GregoryZeng
Forked from lscherub/index.html
Created May 21, 2025 10:09
Show Gist options
  • Save GregoryZeng/789d55b57582a4b6d268dc7b9295345d to your computer and use it in GitHub Desktop.
Save GregoryZeng/789d55b57582a4b6d268dc7b9295345d to your computer and use it in GitHub Desktop.
Wibbly Wobbly SVG Jelly ✨
<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 {
spline,
pointsInPath,
createCoordsTransformer
} from "https://cdn.skypack.dev/@georgedoescode/[email protected]";
import { Vector2D } from "https://cdn.skypack.dev/@georgedoescode/[email protected]";
import gsap from "https://cdn.skypack.dev/[email protected]";
import debounce from "https://cdn.skypack.dev/[email protected]";
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
});
@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;
}

Wibbly Wobbly SVG Jelly ✨

This lil' jelly character is an example from my upcoming tutorial “Creating a Liquid Hover Effect with GSAP & SVG“ — stay tuned for updates! 👀

A Pen by cherub on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment