A dot grid that reacts to your cursor. Dots grow, glow, and connect with lines as you move.
Add these to your <head> or before closing </body> tag:
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/ScrollTrigger.min.js"></script>
[data-spider-grid] {
position: relative;
overflow: hidden;
}
[data-spider-grid] canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
[data-spider-grid] > *:not(canvas) {
position: relative;
z-index: 1;
}
gsap.registerPlugin(ScrollTrigger);
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll("[data-spider-grid]").forEach((el) => {
// --- Read data attributes with defaults ---
const dotColor = el.dataset.dotColor || "#e8782a";
const lineColorRGB= el.dataset.lineColor || "200, 180, 160";
const gridSpacing = parseInt(el.dataset.gridSpacing) || 60;
const dotBase = parseFloat(el.dataset.dotBaseSize) || 2;
const dotMax = parseFloat(el.dataset.dotMaxSize) || 16;
const effectR = parseInt(el.dataset.effectRadius) || 200;
const lineR = parseInt(el.dataset.lineRadius) || 180;
// --- Parse dot color to RGB ---
const tmp = document.createElement("div");
tmp.style.color = dotColor;
document.body.appendChild(tmp);
const parsed = getComputedStyle(tmp).color;
document.body.removeChild(tmp);
const [rBase, gBase, bBase] = parsed.match(/\d+/g).map(Number);
// Dim version (40% brightness)
const rDim = Math.round(rBase * 0.5);
const gDim = Math.round(gBase * 0.5);
const bDim = Math.round(bBase * 0.5);
// --- Create canvas ---
const canvas = document.createElement("canvas");
el.insertBefore(canvas, el.firstChild);
const ctx = canvas.getContext("2d");
let W, H, dots = [];
const mouse = { x: -9999, y: -9999 };
function resize() {
const rect = el.getBoundingClientRect();
W = canvas.width = rect.width;
H = canvas.height = rect.height;
buildGrid();
}
function buildGrid() {
dots = [];
const cols = Math.ceil(W / gridSpacing) + 1;
const rows = Math.ceil(H / gridSpacing) + 1;
const ox = (W - (cols - 1) * gridSpacing) / 2;
const oy = (H - (rows - 1) * gridSpacing) / 2;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
dots.push({ x: ox + c * gridSpacing, y: oy + r * gridSpacing });
}
}
}
// --- Mouse tracking (relative to element) ---
el.style.pointerEvents = "auto";
canvas.style.pointerEvents = "none";
el.addEventListener("mousemove", (e) => {
const rect = el.getBoundingClientRect();
mouse.x = e.clientX - rect.left;
mouse.y = e.clientY - rect.top;
});
el.addEventListener("mouseleave", () => {
mouse.x = -9999;
mouse.y = -9999;
});
el.addEventListener("touchmove", (e) => {
const rect = el.getBoundingClientRect();
mouse.x = e.touches[0].clientX - rect.left;
mouse.y = e.touches[0].clientY - rect.top;
}, { passive: true });
el.addEventListener("touchend", () => {
mouse.x = -9999;
mouse.y = -9999;
});
// --- Animation ---
let active = false;
function animate() {
if (!active) return;
ctx.clearRect(0, 0, W, H);
// Connection lines
for (const dot of dots) {
const dx = dot.x - mouse.x;
const dy = dot.y - mouse.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < lineR) {
const a = (1 - dist / lineR) * 0.5;
ctx.beginPath();
ctx.moveTo(mouse.x, mouse.y);
ctx.lineTo(dot.x, dot.y);
ctx.strokeStyle = `rgba(${lineColorRGB}, ${a})`;
ctx.lineWidth = 1;
ctx.stroke();
}
}
// Dots
for (const dot of dots) {
const dx = dot.x - mouse.x;
const dy = dot.y - mouse.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const t = Math.max(0, 1 - dist / effectR);
const size = dotBase + (dotMax - dotBase) * t;
const r = Math.round(rDim + (rBase - rDim) * t);
const g = Math.round(gDim + (gBase - gDim) * t);
const b = Math.round(bDim + (bBase - bDim) * t);
const alpha = 0.55 + 0.45 * t;
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;
const half = size / 2;
if (size < 4) {
ctx.beginPath();
ctx.arc(dot.x, dot.y, half, 0, Math.PI * 2);
ctx.fill();
} else {
ctx.beginPath();
ctx.roundRect(dot.x - half, dot.y - half, size, size, 3);
ctx.fill();
}
}
// Cursor glow
if (mouse.x > -9000) {
const grad = ctx.createRadialGradient(
mouse.x, mouse.y, 0, mouse.x, mouse.y, 8
);
grad.addColorStop(0, "rgba(255,255,255,0.9)");
grad.addColorStop(1, "rgba(255,255,255,0)");
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, 8, 0, Math.PI * 2);
ctx.fillStyle = grad;
ctx.fill();
}
requestAnimationFrame(animate);
}
// --- ScrollTrigger activation ---
resize();
window.addEventListener("resize", resize);
ScrollTrigger.create({
trigger: el,
start: "top bottom",
end: "bottom top",
onEnter: () => { active = true; animate(); },
onEnterBack: () => { active = true; animate(); },
onLeave: () => { active = false; },
onLeaveBack: () => { active = false; },
});
});
});
<!-- Default orange dots -->
<section data-spider-grid>
<h1>Your content here</h1>
</section>
<!-- Cyan dots, wider spacing -->
<section
data-spider-grid
data-dot-color="#22d3ee"
data-grid-spacing="80"
data-effect-radius="250"
>
<p>Your content here</p>
</section>
<!-- Purple dots, smaller grid -->
<section
data-spider-grid
data-dot-color="#818cf8"
data-line-color="140, 130, 255"
data-grid-spacing="50"
data-dot-max-size="12"
>
<h2>Your content here</h2>
</section>
| Attribute | Default | Description |
|---|---|---|
data-spider-grid |
required | Enables the effect on any element |
data-dot-color |
#e8782a |
Active dot color (any CSS color) |
data-line-color |
200, 180, 160 |
Connection line color as R, G, B values |
data-grid-spacing |
60 |
Distance between dots in pixels |
data-dot-base-size |
2 |
Size of static dots in pixels |
data-dot-max-size |
16 |
Max size of active dots in pixels |
data-effect-radius |
200 |
Radius of the dot grow effect |
data-line-radius |
180 |
Radius of the connection lines |
Add the CSS to your project styles. Paste the JavaScript into a custom
code embed or site-level script. Then add
data-spider-grid
and any optional data attributes to any element via the Settings
panel.
<!-- Webstudio: Add as Custom Code embed -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/ScrollTrigger.min.js"></script>
<script>
// Paste the full JavaScript from Step 3 here
</script>