Magnetic Cursor
Category: Cursor Animations. Last updated: Aug 20, 2025
Setup — External Scripts
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script><script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/Flip.min.js"></script>Code
<!-- Cursor follower -->
<div class="cursor">
<div class="cursor-bg"></div>
</div>
<!-- Example of a link -->
<a data-magnetic-cursor-target href="#" class="magnetic-link">
<div class="magnetic-link__inner">
<span class="magnetic-link__label">Here’s a link</span>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 14 32" fill="none" class="magnetic-link__icon">
<path d="M3 24L11 16L3 8" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round"></path>
</svg>
</div>
<div data-magnetic-cursor-bg class="magnetic-link__bg"></div>
</a>.magnetic-link {
color: inherit;
padding: .5em .875em;
text-decoration: none;
position: relative;
}
.magnetic-link__inner {
z-index: 1;
grid-column-gap: .35em;
grid-row-gap: .35em;
justify-content: center;
align-items: center;
display: flex;
position: relative;
}
.magnetic-link__icon {
justify-content: center;
align-items: center;
width: .4em;
margin-bottom: -.125em;
display: flex;
}
.magnetic-link__bg {
z-index: 0;
border-radius: .25em;
position: absolute;
inset: 0%;
}
.cursor {
aspect-ratio: 1;
border-radius: 100em;
width: .75em;
position: fixed;
inset: 0% auto auto 0%;
}
.cursor-bg {
border-radius: inherit;
background-color: #fff;
width: 100%;
height: 100%;
}
.magnetic-link .cursor-bg {
opacity: 0.1;
}
.magnetic-link .cursor-bg {
opacity: 0.1;
}function initMagneticCursor() {
gsap.registerPlugin(Flip);
const cursor = document.querySelector(".cursor");
if(!cursor) return;
const cursorBg = cursor.querySelector(".cursor-bg");
// First, make the cursor div follow our mouse
gsap.set(".cursor", {xPercent:-50, yPercent: -50});
let xTo = gsap.quickTo(".cursor", "x", {duration: 0.6, ease: "power3"});
let yTo = gsap.quickTo(".cursor", "y", {duration: 0.6, ease: "power3"});
window.addEventListener("mousemove", e => {
xTo(e.clientX);
yTo(e.clientY);
});
// Then, make the setup for our flipping button
const hoverTargets = document.querySelectorAll("[data-magnetic-cursor-target]");
if(!hoverTargets.length) return;
hoverTargets.forEach((target) => {
const bgHolder = target.querySelector("[data-magnetic-cursor-bg]");
target.addEventListener("mouseenter", () =>{
// Register the 'state'
const state = Flip.getState(cursorBg);
// Add bg element into the link
bgHolder.appendChild(cursorBg);
// Create the Flip animation
Flip.from(state,{
ease:"back.out(1)",
duration: 0.3
});
});
target.addEventListener("mouseleave", () =>{
// Register 'state' again, include opacity for subtle ease
const state = Flip.getState(cursorBg,{
props:"opacity"
});
// Add bg element back into the cursor
cursor.appendChild(cursorBg);
// Flip for a smooth animation
Flip.from(state,{
ease:"power4.out",
duration: 0.5,
});
});
});
}
// Initialize Magnetic Cursor
document.addEventListener('DOMContentLoaded', () => {
initMagneticCursor();
});Guide
Implementation
Cursor
Use a fixed positioned element .cursor that follows the pointer via gsap.quickTo, and ensure it contains a single .cursor-bg child that flips between the cursor and targets.
Targets
Use [data-magnetic-cursor-target] to mark any hoverable element that should 'capture' the cursor’s background, enabling the Flip animation from the global cursor into the target.
Background Slot
Place a single child with [data-magnetic-cursor-bg] inside each target; this element acts as the drop-zone where the cursor’s .cursor-bg is appended on hover and Flip-animated into place. This doesn't necessarily have to be a full background, definitely checkout the live preview link to see how you can apply this script.
Flip Behavior
On mouseenter of a target, the script 'captures' state with Flip.getState(cursorBg) and appends the background element into the target’s [data-magnetic-cursor-bg], then Flip animates it by doing all the necessary calculations itself. On mouseleave, it 'captures' the state again, adds .cursor-bg back into .cursor, and flips it out smoothly.
Hover Styling
Style the cursor background while it lives inside a target using a parent selector, so the visual treatment changes only when the Flip has moved it into [data-magnetic-cursor-bg]. Here's an example: