// FREE TOOLSView all →
◈Keyword ResearchFreeReal Google + Bing suggestions, grouped by intent.
▣Meta & SERP PreviewsoonSee how your page looks in Google, fix title & description.
◐AI Visibility ChecksoonWhat AI assistants say about your brand.
❖Schema GeneratorsoonCopy-paste JSON-LD for FAQ, Article, Product & more.
← all components
#02
Gooey liquid cursor comet
A gooey liquid comet that elastically chases your cursor, a chain of blobs merged into one liquid mass with a blur+contrast filter and a glowing gradient tail. Canvas-based, 60fps.
ReactCanvasJS
// LIVE DEMO
Liquid follows you
A gooey comet that chases your cursor with elastic lag.
// THE CODE
React
"use client";
import { useEffect, useRef } from "react";
export default function LiquidComet() {
const ref = useRef(null);
useEffect(() => {
const cv = ref.current, ctx = cv.getContext("2d");
if (matchMedia("(prefers-reduced-motion: reduce)").matches) return;
let w, h, raf, t = 0;
const DPR = Math.min(devicePixelRatio || 1, 2);
const target = { x: 0, y: 0 };
const blobs = Array.from({ length: 9 }, (_, i) => ({
x: 0, y: 0, r: 58 - i*4.5, lag: 0.22 - i*0.02, hue: 190 + i*9,
}));
function resize() {
const r = cv.getBoundingClientRect(); w = r.width; h = r.height;
cv.width = w*DPR; cv.height = h*DPR; ctx.setTransform(DPR,0,0,DPR,0,0);
if (target.x === 0) { target.x = w/2; target.y = h/2; blobs.forEach(b => { b.x = w/2; b.y = h/2; }); }
}
function frame() {
t += .016;
ctx.fillStyle = "rgba(6,9,18,.22)"; ctx.fillRect(0,0,w,h);
blobs[0].x += (target.x - blobs[0].x)*blobs[0].lag;
blobs[0].y += (target.y - blobs[0].y)*blobs[0].lag;
for (let i = 1; i < blobs.length; i++) {
blobs[i].x += (blobs[i-1].x - blobs[i].x)*(blobs[i].lag + .12);
blobs[i].y += (blobs[i-1].y - blobs[i].y)*(blobs[i].lag + .12);
}
ctx.save(); ctx.filter = "blur(12px) contrast(26)"; ctx.globalCompositeOperation = "lighter";
for (let i = 0; i < blobs.length; i++) {
const b = blobs[i], wob = 1 + Math.sin(t*2.4 + i*.7)*.18;
const g = ctx.createRadialGradient(b.x,b.y,0,b.x,b.y,b.r*wob);
g.addColorStop(0,`hsl(${b.hue},95%,62%)`); g.addColorStop(1,`hsl(${b.hue},95%,50%)`);
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(b.x,b.y,b.r*wob,0,7); ctx.fill();
}
ctx.restore(); raf = requestAnimationFrame(frame);
}
const onMove = e => { const r = cv.getBoundingClientRect(); target.x = e.clientX - r.left; target.y = e.clientY - r.top; };
resize(); frame();
addEventListener("resize", resize); cv.addEventListener("pointermove", onMove);
return () => { cancelAnimationFrame(raf); removeEventListener("resize", resize); cv.removeEventListener("pointermove", onMove); };
}, []);
return <canvas ref={ref} className="w-full h-[360px] rounded-2xl bg-[#060912]" />;
}// HOW IT WORKS
- 01A chain of circles follows the cursor, each one chasing the blob ahead of it with a slightly different lag, so it stretches into a comet tail.
- 02A blur + high-contrast canvas filter fuses the overlapping circles into a single smooth liquid shape (the metaball trick).
- 03Additive blend and a translucent fill each frame add glow and a soft motion trail. Respects prefers-reduced-motion.
Want a custom component for your product?
Let's build it →