webdevpuneet.com

Magnetic Grid — Free HTML CSS JS Cursor Attraction Snippet

Magnetic Grid · Animations · Plain HTML, CSS & JS · Live preview

CategoryAnimations

Copy & paste

Source Code

<section class="mg-stage" id="mgStage">
  <div class="mg-grid" id="mgGrid"></div>
  <div class="mg-caption">Move your cursor — nearby dots are pulled toward it</div>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Code-built dot field
Grid sized and filled from constants.
Distance-based pull
Dots displace toward the cursor.
Smooth falloff
Force fades from center to radius edge.
Color highlight
In-range dots tint to the accent.
Single rAF loop
One loop updates the whole grid.
Cheap move handler
Pointer position kept in shared variables.
GPU transforms
translate avoids layout thrash.
Rebuild on resize
Field re-fits the viewport.

About this UI Snippet

Magnetic Grid — Dots Pulled Toward the Cursor With Falloff

Screenshot of the Magnetic Grid snippet rendered live

The magnetic grid is the tactile backdrop where a field of evenly-spaced dots reacts to your cursor like iron filings to a magnet: dots within a radius are pulled toward the pointer and tinted, with the pull strongest right under the cursor and fading to nothing at the edge of its influence. This snippet builds it with plain HTML, CSS, and one vanilla JavaScript animation loop.

Building the dot field

JavaScript fills a CSS grid with cells sized to a CELL constant, each holding a small dot, and computes how many columns and rows fit the available space. The dots are stored in a flat array for fast iteration. Generating the grid in code means resizing is trivial — a resize listener rebuilds the field to fit the new viewport — and the density is controlled by a single number.

Distance-based magnetic pull

The heart of the effect is the per-frame loop. For each dot, it measures the vector from the dot's center to the cursor (dx, dy) and the distance between them. If the dot is within RADIUS (130px), it's displaced toward the cursor by translate(dx * force, dy * force). The crucial detail is the falloff: force = (1 - dist / RADIUS) * PULL, so a dot directly under the cursor gets the full pull while a dot at the edge of the radius gets nearly zero. This gradient of force is what makes it look like a smooth magnetic field rather than a hard on/off zone.

Color as a second cue

Dots inside the radius also switch from the muted base color to the accent, with a CSS transition on background so they fade in and out of the highlight. The color change makes the magnet's area of influence visible even where the displacement is subtle, reinforcing the sense of a field moving with your cursor.

One loop for the whole grid

A single requestAnimationFrame loop updates every dot each frame by reading the latest pointer position from two variables set in pointermove. Keeping the pointer position in shared variables (rather than doing work inside the move handler) means the move handler is nearly free and all the math happens in one place at the display's refresh rate. When the pointer leaves, the position is parked far off-screen so every dot relaxes back to rest.

Why transform, not layout

Dots are displaced with transform: translate, which the GPU handles without triggering layout or paint, and they carry will-change: transform. That's what lets hundreds of dots move every frame smoothly — animating left/top instead would thrash layout and stutter.

Customizing it

Change CELL for a denser or sparser field, RADIUS for a bigger or smaller magnet, and PULL for a stronger or gentler attraction. Recolor the accent, or invert the force sign to make dots flee the cursor instead. Swap dots for small icons or characters. Pair it with a background boxes hero or a text generate headline for an interactive section.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA field of evenly spaced dots fills the stage.
  2. 2
    Move your cursorNearby dots are pulled toward it and tint to the accent.
  3. 3
    Watch the falloffDots closest to the cursor move most; distant ones barely.
  4. 4
    Move the pointer awayDots relax smoothly back to their grid positions.
  5. 5
    Resize the windowThe grid rebuilds to fit the new space.
  6. 6
    Tune the magnetAdjust CELL, RADIUS, and PULL constants.

Real-world uses

Common Use Cases

Interactive hero backdrops
Place a field of dots behind a text generate headline, with dots pulled toward the cursor like iron filings toward a magnet.
Pairing with box-grid sections
Pair with a background boxes grid in an adjacent section, using one requestAnimationFrame loop for the whole field.
Developer tool sites
Match an animated grid background elsewhere on the page, with in-range dots tinting to the accent colour.
Lost-page playgrounds
Liven up an empty state page, where the dots respond to the pointer so a dead end becomes something fun to play with.
Distance falloff reference
Study how force fades smoothly from the cursor to the edge of the radius, so the pull never looks abrupt or binary.
Related: Traffic Light FSM Visualizer
See the Traffic Light FSM Visualizer for a related animations pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Each frame, the loop measures the vector from each dot's center to the cursor and the distance between them. If the dot is within the radius, it's displaced by translate(dx * force, dy * force) toward the pointer. So dots move along the line to the cursor, which reads as magnetic attraction.

The force has a falloff: force = (1 - dist / radius) * PULL. A dot directly under the cursor gets the full pull, while one at the edge of the radius gets nearly zero. That gradient of displacement makes the influence fade out smoothly toward the edge, like a real field, rather than dots snapping at a hard boundary.

transform: translate is composited on the GPU without triggering layout or paint, and the dots carry will-change: transform. That lets hundreds of dots update every frame smoothly. Animating left/top would force layout recalculation each frame and stutter, especially on a dense grid.

No. The move handler only stores the cursor x and y in two variables; all the per-dot math happens once per frame in the single requestAnimationFrame loop. This keeps the handler nearly free and centralizes the work at the display's refresh rate. On pointerleave the position is parked off-screen so every dot relaxes back.

Build the dot field from a sized array and keep the pointer position and the dot elements in refs. Run the rAF loop and the resize/rebuild in a mount effect with cleanup that cancels the frame on unmount. Avoid putting per-frame transforms in state. The CSS ports directly; in Tailwind, lay out the grid with grid utilities and apply transforms inline from the loop.