<div class="gooey-wrapper">
<div class="content-text">
<h1>Liquid Cursor</h1>
<p>Move your mouse around to see the gooey effect merge dynamically.</p>
</div>
<svg style="display: none;">
<defs>
<filter id="goo">
<feGaussianBlur in="SourceGraphic" stdDeviation="15" result="blur"></feGaussianBlur>
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 25 -10" result="goo"></feColorMatrix>
<feBlend in="SourceGraphic" in2="goo"></feBlend>
</filter>
</defs>
</svg>
<div class="cursor-container" id="cursor-container">
<!-- Blobs will be injected instantly by Javascript -->
</div>
</div>
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
margin: 0;
background: #fafafa;
min-height: 100vh;
overflow: hidden;
cursor: none;
}
.gooey-wrapper {
width: 100%;
height: 100vh;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.content-text {
text-align: center;
color: #111827;
z-index: 10;
pointer-events: none;
}
.content-text h1 {
font-size: clamp(3rem, 8vw, 5rem);
margin: 0 0 10px 0;
letter-spacing: -1px;
}
.content-text p {
font-size: 1.25rem;
color: #4b5563;
margin: 0;
}
.cursor-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
filter: url("#goo");
}
.blob {
position: absolute;
border-radius: 50%;
background: #3b82f6;
transform: translate(-50%, -50%);
will-change: left, top, width, height;
}
const container = document.getElementById("cursor-container");
const blobs = new Array();
const numBlobs = 6;
let mouseX = window.innerWidth / 2;
let mouseY = window.innerHeight / 2;
window.addEventListener("mousemove", function(e) {
mouseX = e.clientX;
mouseY = e.clientY;
});
// Setup the trailing blobs
for (let i = 0; i < numBlobs; i++) {
const el = document.createElement("div");
el.className = "blob";
container.appendChild(el);
const blobObj = new Object();
blobObj.el = el;
blobObj.x = mouseX;
blobObj.y = mouseY;
const size = 60 - (i * 8);
el.style.width = size + "px";
el.style.height = size + "px";
blobs.push(blobObj);
}
function animateGoo() {
let targetX = mouseX;
let targetY = mouseY;
blobs.forEach(function(blobObj) {
// Interpolation makes the followers lag gently behind
blobObj.x += (targetX - blobObj.x) * 0.3;
blobObj.y += (targetY - blobObj.y) * 0.3;
blobObj.el.style.left = blobObj.x + "px";
blobObj.el.style.top = blobObj.y + "px";
targetX = blobObj.x;
targetY = blobObj.y;
});
window.requestAnimationFrame(animateGoo);
}
animateGoo();