Magnetic Letters
Eight letters on a dark background drift toward the cursor when it comes within 100 px, then spring back to their place; a click reverses direction and repels them. Force is proportional to distance — the closer the cursor, the stronger the attraction (or repulsion).
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Hover has 48 effects, including 5 free. Explore the category →
3 usage examples



How it works
Each letter is a <span class="ml379-letter"> inside the .ml379-container. On every frame of a requestAnimationFrame loop, the script computes the distance between each letter's centre and the mouse position in the container's coordinate system. If the distance is below 100 px, the letter is offset by a translate(dx, dy) whose magnitude is distance × force × 0.5 × direction: force is (100 − dist) / 100, so 1 at the cursor and 0 at 100 px. Direction is +1 (attraction) or −1 (repulsion) depending on the current state, toggled on each click on the container. When the mouse is more than 100 px away or outside the container, the letter returns to translate(0, 0) via the CSS transition: transform 0.1s ease-out.
Each letter's colour shifts from #6366f1 (at rest) to hsl(280 + i × 15, 70%, 60%) when inside the influence zone, with i its rank in the list (violet to pink). When the cursor leaves the container, mouseleave snaps all letters back instantly. The touchmove event (with preventDefault()) provides the same behaviour on a touchscreen. An IntersectionObserver pauses the loop when the effect is off screen.
Accessibility
- Role and label: the container has
role="img"andaria-label="MAGNETIC"; the eight letters havearia-hidden="true". A screen reader announces "MAGNETIC" once and ignores the letter movement. - Keyboard focus: the container has
tabindex="0"and a:focus-visibleoutline (2 px indigo). On focus, the mouse is virtually placed at the container's centre, triggering attraction on the middle letters; on blur, the letters return to their place. - Touch:
touchmovemoves letters toward the finger exactly like the mouse.touchendresets them. Page scrolling is blocked (preventDefault) while the finger is inside the container. - Reduced motion: under
prefers-reduced-motion: reduce, the loop does not start andtransition: noneis forced on the letters; they stay still, the label is always readable. - Click: a click toggles between attraction and repulsion. No button role is added on the container (
role="img"takes priority); if the toggle is functional in your context, replace the role withrole="button"and add akeydownhandler for Enter/Space.
Browser compatibility
Pure DOM and CSS, no dependency. IntersectionObserver available since Chrome 51, Firefox 55, Safari 12.1. requestAnimationFrame since Chrome 10, Firefox 11, Safari 6. CSS transitions and window.matchMedia cover all modern browsers. Not verified on real Safari; expected full.
Without JavaScript, letters display statically with no behaviour. Without IntersectionObserver, the loop also runs off screen.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0379-root">
<div class="ml379-container" role="img" aria-label="MAGNETIC" tabindex="0">
<span class="ml379-letter" aria-hidden="true">M</span>
<span class="ml379-letter" aria-hidden="true">A</span>
<span class="ml379-letter" aria-hidden="true">G</span>
<span class="ml379-letter" aria-hidden="true">N</span>
<span class="ml379-letter" aria-hidden="true">E</span>
<span class="ml379-letter" aria-hidden="true">T</span>
<span class="ml379-letter" aria-hidden="true">I</span>
<span class="ml379-letter" aria-hidden="true">C</span>
</div>
<span class="ml379-label" aria-hidden="true">Move the mouse near the letters</span>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Influence radius (JS — if(dist < 100)) |
100 px | 60 px gives a more precise, less intrusive effect; 150 px pulls letters from halfway across the container. |
Displacement intensity (JS — force * 0.5) |
0.5 | 0.8 makes the movement more dramatic; 0.2 gives a barely perceptible breath. |
Default mode (JS — var isAttract = true) |
attraction | Initialise to false to start in repulsion. |
Letter text (HTML — eight <span class="ml379-letter">) |
MAGNETIC (8 letters) | Replace with any word of 4 to 12 letters; beyond 10, reduce the font to 1.8 rem to avoid overflow on mobile. |
Return speed (CSS — transition: transform 0.1s) |
0.1 s | 0.3 s gives a lazy return; 0.04 s a near-instant snap. |
FAQ
role="img" with role="button", add aria-label="Toggle attraction / repulsion" and a keydown handler that calls the toggle on Enter and Space. Make sure the :focus-visible outline stays visible.span.ml379-letter elements it finds in the container. On mobile at 390 px, 10 letters at 2.5 rem fit on one line without overflow; beyond that, flex-wrap sends them to a second row, shifting their centres and giving a two-level effect.