<canvas id="canvas"></canvas>
body {
margin: 0;
}
#canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
const SVG = `
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 718 440" width="718" height="440">
<path d="M179.4,433.6h-0.1l167.9-215.5c-23.2,1.5-46.5,1.2-69.7-1.5c-0.3,0.1-0.5,0.1-0.9-0.1c-5.6-0.6-11.3-1.3-16.8-2.2
c-4.6-0.6-9.4-1.4-14-2.3c-4.4-0.8-8.7-1.7-13-2.6c-4.4-0.9-8.7-1.9-13-3.1c-4.2-1-8.6-2.2-12.8-3.5c-2.8-0.8-5.5-1.7-8.3-2.6
c-2.2-0.6-4.2-1.3-6.4-2.1c-3.8-1.3-7.8-2.6-11.7-4.1c-8-3-15.9-6-23.6-9.5c-4.1-1.8-8.2-3.7-12.2-5.6c-3.2-1.5-6.3-3.1-9.4-4.9
c-11.6-5.9-22.6-12.2-32.9-19.1l-17.6-11.6l0.5-0.5C59,123.5,34.9,100.6,13.9,74.7l92.5-78.5c15.3,19.6,35.2,35.1,55.1,49.2l0.3-0.3
l17.8,10.3c6.4,3.7,13.2,7.4,19.5,10.5c51.6,24.8,108.6,32.7,163.4,24.4c35.2-5.4,69.6-17.5,101-36.3c5.9-3.4,11.2-7,16.6-10.8
c3-2.8,7.8-7.3,10.7-10.2l20.6-26.4l7.3-9.2l0.4,0.3l93.4,72.8c0,0-8.8,13.5-27.1,45.1c-1.4,3.1-2.8,6.3-3.9,9.6
c-34.3,93.8-15.9,199,47.9,274.9"/>
</svg>`;
const imageURL = "data:image/svg+xml;base64," + btoa(SVG);
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const gridStep = 10;
const circleRadius = 4;
const customDistance = 120;
(async function () {
const points = [];
const maskImage = await loadImg(imageURL);
if (maskImage.height === 0) throw new Error("svg mask height is 0");
const scaleMask = Math.min(
canvas.width / maskImage.width,
canvas.height / maskImage.height
);
const maskCanvas = document.createElement("canvas");
const maskCtx = maskCanvas.getContext("2d");
maskCanvas.width = canvas.width;
maskCanvas.height = canvas.height;
maskCtx.save();
maskCtx.translate(maskCanvas.width / 2, maskCanvas.height / 2);
maskCtx.scale(scaleMask, scaleMask);
maskCtx.translate(-maskImage.width / 2, -maskImage.height / 2);
maskCtx.drawImage(maskImage, 0, 0, maskImage.width, maskImage.height);
maskCtx.restore();
const maskData = maskCtx.getImageData(
0,
0,
maskCanvas.width,
maskCanvas.height
);
for (let y = 0; y < maskData.height; y += gridStep) {
for (let x = 0; x < maskData.width; x += gridStep) {
const index = (y * maskData.width + x) * 4;
const alpha = maskData.data[index + 3];
if (alpha > 128) {
points.push({ x, y, tx: 0, ty: 0 });
}
}
}
window.addEventListener("mousemove", (e) => {
const mx = e.clientX;
const my = e.clientY;
points.forEach((p) => {
const deltaX = Math.floor(p.x - mx) * -0.45;
const deltaY = Math.floor(p.y - my) * -0.45;
const distance = Math.hypot(deltaX, deltaY);
if (distance < customDistance) {
gsap.to(p, { ty: deltaY, tx: deltaX, duration: 0.5, });
} else {
gsap.to(p, { ty: 0, tx: 0, duration: 0.6, });
}
});
});
gsap.ticker.add(() => draw(ctx, points));
})();
function draw(ctx, points) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
points.forEach(p => {
ctx.moveTo(p.x + p.tx, p.y + p.ty);
ctx.arc(p.x + p.tx, p.y + p.ty, circleRadius, 0, 2 * Math.PI);
});
ctx.fill();
}
function loadImg(src) {
return new Promise((resolve) => {
const img = new Image();
img.addEventListener("load", () => resolve(img));
img.crossOrigin = "Anonymous";
img.src = src;
});
}
//
This Pen doesn't use any external CSS resources.