22 CSS Transition Effects 20 / 22

Magnetic Button Effect

Buttons, icon links, nav items and FABs that translate toward the cursor within a 120px radius using mousemove vector math and spring easing.

CSS + JS MIT licensed
Live Demo Open in tab

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

Open in playground

The code

<div class="page">
  <h1>Magnetic Button Effect</h1>
  <p class="subtitle">Move your cursor near the buttons — they'll pull toward it</p>
  <p class="hint">↓ hover to activate magnetism</p>

  <!-- 1. CTA buttons -->
  <section>
    <h2>Call-to-Action Buttons</h2>
    <div class="cta-row">
      <div class="mag-wrap" data-strength="0.4">
        <button class="btn-mag btn-solid"><span class="label">Get Started</span></button>
      </div>
      <div class="mag-wrap" data-strength="0.4">
        <button class="btn-mag btn-purple"><span class="label">Upgrade Plan</span></button>
      </div>
      <div class="mag-wrap" data-strength="0.4">
        <button class="btn-mag btn-outline"><span class="label">Learn More</span></button>
      </div>
    </div>
  </section>

  <!-- 2. Icon buttons -->
  <section>
    <h2>Social / Icon Buttons</h2>
    <div class="icon-row">
      <div class="mag-wrap" data-strength="0.5">
        <button class="btn-icon bi-dark"><span class="label">🌐</span></button>
      </div>
      <div class="mag-wrap" data-strength="0.5">
        <button class="btn-icon bi-blue"><span class="label">𝕏</span></button>
      </div>
      <div class="mag-wrap" data-strength="0.5">
        <button class="btn-icon bi-insta"><span class="label">📸</span></button>
      </div>
      <div class="mag-wrap" data-strength="0.5">
        <button class="btn-icon bi-gh"><span class="label">🐙</span></button>
      </div>
      <div class="mag-wrap" data-strength="0.5">
        <button class="btn-icon bi-li"><span class="label">in</span></button>
      </div>
    </div>
  </section>

  <!-- 3. Nav -->
  <section>
    <h2>Navigation Items</h2>
    <nav class="nav-mag">
      <div class="mag-wrap" data-strength="0.25"><button class="nav-item active">Home</button></div>
      <div class="mag-wrap" data-strength="0.25"><button class="nav-item">Work</button></div>
      <div class="mag-wrap" data-strength="0.25"><button class="nav-item">About</button></div>
      <div class="mag-wrap" data-strength="0.25"><button class="nav-item">Blog</button></div>
      <div class="mag-wrap" data-strength="0.25"><button class="nav-item">Contact</button></div>
    </nav>
  </section>

  <!-- 4. FABs -->
  <section>
    <h2>Floating Action Buttons</h2>
    <div class="fab-cluster">
      <div class="mag-wrap" data-strength="0.45">
        <button class="btn-mag fab fab-primary"><span class="label">✚ New Project</span></button>
      </div>
      <div class="mag-wrap" data-strength="0.5">
        <button class="btn-icon fab fab-sm"><span class="label">🔍</span></button>
      </div>
      <div class="mag-wrap" data-strength="0.5">
        <button class="btn-icon fab fab-sm"><span class="label">⚙️</span></button>
      </div>
      <div class="mag-wrap" data-strength="0.5">
        <button class="btn-icon fab fab-sm"><span class="label">📤</span></button>
      </div>
    </div>
  </section>
</div>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #09090f; min-height: 100vh; overflow-x: hidden; }

.page { padding: 70px 40px; max-width: 1000px; margin: 0 auto; }
h1 { color: #fff; text-align: center; font-size: 2rem; margin-bottom: 8px; }
.subtitle { color: rgba(255,255,255,.4); text-align: center; margin-bottom: 50px; }
section { margin-bottom: 70px; }
section > h2 { color: rgba(255,255,255,.35); font-size: .72rem; text-transform: uppercase; letter-spacing: 3px; margin-bottom: 28px; }

/* Shared magnetic wrapper */
.mag-wrap {
  display: inline-flex; position: relative;
  /* JS will translate this on mousemove */
  transition: transform .15s cubic-bezier(.25,.46,.45,.94);
}

/* ─────────────────────────────────────
   1. Primary call-to-action buttons
───────────────────────────────────── */
.cta-row { display: flex; flex-wrap: wrap; gap: 28px; align-items: center; }

.btn-mag {
  position: relative; overflow: hidden;
  padding: 16px 40px; border-radius: 50px; border: none; cursor: pointer;
  font-size: 1rem; font-weight: 700; letter-spacing: .5px;
  transition: transform .15s cubic-bezier(.25,.46,.45,.94), box-shadow .25s;
}
/* Inner text span — also shifts slightly */
.btn-mag .label {
  position: relative; z-index: 1; pointer-events: none;
  display: block; transition: transform .15s cubic-bezier(.25,.46,.45,.94);
}

.btn-solid {
  background: #fff; color: #09090f;
  box-shadow: 0 4px 24px rgba(255,255,255,.1);
}
.btn-solid:hover { box-shadow: 0 8px 40px rgba(255,255,255,.25); }

.btn-purple {
  background: linear-gradient(135deg, #7c3aed, #4f46e5); color: #fff;
  box-shadow: 0 4px 24px rgba(124,58,237,.2);
}
.btn-purple:hover { box-shadow: 0 8px 40px rgba(124,58,237,.5); }

.btn-outline {
  background: transparent; color: #fff;
  border: 1.5px solid rgba(255,255,255,.3);
  box-shadow: none;
}
.btn-outline:hover { border-color: rgba(255,255,255,.8); box-shadow: 0 0 32px rgba(255,255,255,.1); }

/* Ripple on click */
.btn-mag .ripple-burst {
  position: absolute; border-radius: 50%;
  background: rgba(255,255,255,.3); pointer-events: none;
  transform: scale(0); animation: burst .55s ease-out forwards;
}
@keyframes burst {
  to { transform: scale(3); opacity: 0; }
}

/* ─────────────────────────────────────
   2. Icon / social buttons
───────────────────────────────────── */
.icon-row { display: flex; flex-wrap: wrap; gap: 20px; align-items: center; }

.btn-icon {
  width: 56px; height: 56px; border-radius: 50%; border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center; font-size: 1.3rem;
  transition: transform .15s cubic-bezier(.25,.46,.45,.94), box-shadow .25s;
}
.btn-icon .label { display: flex; align-items: center; justify-content: center; }

.bi-dark   { background: #1a1a2e; border: 1px solid rgba(255,255,255,.1); color: #fff; }
.bi-dark:hover { box-shadow: 0 8px 30px rgba(0,0,0,.6); }
.bi-blue   { background: #1d9bf0; color: #fff; }
.bi-blue:hover { box-shadow: 0 8px 30px rgba(29,155,240,.4); }
.bi-insta  { background: linear-gradient(135deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888); color: #fff; }
.bi-insta:hover { box-shadow: 0 8px 30px rgba(220,39,67,.4); }
.bi-gh     { background: #24292f; color: #fff; border: 1px solid rgba(255,255,255,.1); }
.bi-gh:hover { box-shadow: 0 8px 30px rgba(0,0,0,.6); }
.bi-li     { background: #0077b5; color: #fff; }
.bi-li:hover { box-shadow: 0 8px 30px rgba(0,119,181,.4); }

/* ─────────────────────────────────────
   3. Nav links
───────────────────────────────────── */
.nav-mag {
  display: flex; gap: 0; align-items: center;
  background: rgba(255,255,255,.04); border-radius: 16px; padding: 6px;
  border: 1px solid rgba(255,255,255,.08); width: fit-content;
}
.nav-item {
  padding: 10px 22px; border-radius: 10px; border: none; cursor: pointer;
  background: transparent; color: rgba(255,255,255,.6); font-size: .875rem; font-weight: 500;
  transition: background .2s, color .2s, transform .15s cubic-bezier(.25,.46,.45,.94);
  position: relative;
}
.nav-item.active { background: rgba(255,255,255,.1); color: #fff; }

/* ─────────────────────────────────────
   4. Floating action cluster
───────────────────────────────────── */
.fab-cluster { display: flex; gap: 16px; align-items: center; }

.fab {
  border-radius: 18px; border: none; cursor: pointer;
  display: flex; align-items: center; gap: 10px;
  font-size: .9rem; font-weight: 600; color: #fff;
  transition: transform .15s cubic-bezier(.25,.46,.45,.94), box-shadow .25s;
}
.fab-primary { padding: 14px 28px; background: linear-gradient(135deg,#10b981,#059669); }
.fab-primary:hover { box-shadow: 0 10px 40px rgba(16,185,129,.4); }
.fab-sm { width: 48px; height: 48px; border-radius: 14px; padding: 0; justify-content: center; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.12); font-size: 1.1rem; }
.fab-sm:hover { background: rgba(255,255,255,.14); }

/* ─── Cursor hint ─── */
.hint { color: rgba(255,255,255,.2); font-size: .8rem; text-align: center; margin-top: -30px; margin-bottom: 50px; }
const RADIUS = 120; // activation radius in px

  document.querySelectorAll('.mag-wrap').forEach(wrap => {
    const strength = parseFloat(wrap.dataset.strength) || 0.4;
    const btn = wrap.querySelector('button');
    const label = btn.querySelector('.label');

    wrap.addEventListener('mousemove', e => {
      const r = wrap.getBoundingClientRect();
      const cx = r.left + r.width / 2;
      const cy = r.top  + r.height / 2;
      const dx = e.clientX - cx;
      const dy = e.clientY - cy;
      const dist = Math.hypot(dx, dy);

      if (dist < RADIUS) {
        const factor = (1 - dist / RADIUS) * strength;
        const tx = dx * factor;
        const ty = dy * factor;
        wrap.style.transform = `translate(${tx}px, ${ty}px)`;
        if (label) label.style.transform = `translate(${tx * .4}px, ${ty * .4}px)`;
        btn.style.transform = '';
      }
    });

    wrap.addEventListener('mouseleave', () => {
      wrap.style.transform = '';
      if (label) label.style.transform = '';
    });

    // Ripple on click
    btn.addEventListener('click', e => {
      const r = btn.getBoundingClientRect();
      const x = e.clientX - r.left;
      const y = e.clientY - r.top;
      const span = document.createElement('span');
      span.className = 'ripple-burst';
      const s = Math.max(r.width, r.height);
      span.style.cssText = `width:${s}px;height:${s}px;left:${x - s/2}px;top:${y - s/2}px`;
      btn.appendChild(span);
      setTimeout(() => span.remove(), 600);
    });
  });

  // Nav active state
  document.querySelectorAll('.nav-item').forEach(b => {
    b.addEventListener('click', () => {
      document.querySelectorAll('.nav-item').forEach(x => x.classList.remove('active'));
      b.classList.add('active');
    });
  });

Search CodeFronts

Loading…