Magnetic button
The button is gently pulled toward the cursor while hovered, then springs back when you leave.
CSSJavaScriptIntermediate
<div class="zone" id="zone"><button class="btn" id="btn">Hover me</button></div>
.zone{padding:60px;display:grid;place-items:center}
.btn{font:inherit;font-weight:700;font-size:1.1rem;color:#270100;background:#f08a5d;border:0;border-radius:999px;padding:18px 34px;cursor:pointer;
transition:transform .5s cubic-bezier(.2,.9,.25,1.3)}
.btn.moving{transition:transform .12s linear}
.btn:active{scale:.96}
const zone = document.getElementById('zone');
const btn = document.getElementById('btn');
const strength = 0.35;
if (matchMedia('(hover:hover) and (pointer:fine)').matches) {
zone.addEventListener('pointermove', e => {
const r = btn.getBoundingClientRect();
const dx = e.clientX - (r.left + r.width / 2);
const dy = e.clientY - (r.top + r.height / 2);
btn.classList.add('moving');
btn.style.transform = `translate(${dx * strength}px, ${dy * strength}px)`;
});
zone.addEventListener('pointerleave', () => {
btn.classList.remove('moving');
btn.style.transform = ''; // springs back via the slow transition
});
}
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Magnetic button</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{display:grid;place-items:center;background:#140807;color:#fffffc;font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
.zone{padding:60px;display:grid;place-items:center}
.btn{font:inherit;font-weight:700;font-size:1.1rem;color:#270100;background:#f08a5d;border:0;border-radius:999px;padding:18px 34px;cursor:pointer;
transition:transform .5s cubic-bezier(.2,.9,.25,1.3)}
.btn.moving{transition:transform .12s linear}
.btn:active{scale:.96}
</style></head>
<body>
<div class="zone" id="zone"><button class="btn" id="btn">Hover me</button></div>
<script>
const zone = document.getElementById('zone');
const btn = document.getElementById('btn');
const strength = 0.35;
if (matchMedia('(hover:hover) and (pointer:fine)').matches) {
zone.addEventListener('pointermove', e => {
const r = btn.getBoundingClientRect();
const dx = e.clientX - (r.left + r.width / 2);
const dy = e.clientY - (r.top + r.height / 2);
btn.classList.add('moving');
btn.style.transform = `translate(${dx * strength}px, ${dy * strength}px)`;
});
zone.addEventListener('pointerleave', () => {
btn.classList.remove('moving');
btn.style.transform = ''; // springs back via the slow transition
});
}
</script>
</body></html>
Good for
Primary calls to action on portfolio and agency style sites.
Watch out for
Only enable on devices with a real hover pointer, and keep the pull small (a few pixels), or it feels broken.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.