← Blurr Motion cta-magnetic-button-minimal
Categorie cta Tier 1 Techniek #12 Deps

Magnetisch

Een knop die
weet dat je kijkt.


Beweging volgt aandacht. Niets meer.

1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: cta-magnetic-button-minimal
// Subtle magnetic-tilt op cursor (0.3 * delta, capped). Auto-demo: lemniscaat
// (∞-curve) bij viewport-enter zodat de knop automatisch zwiept zonder hover.
const root = document.querySelector('.mag-block');
if (root && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  const btn = root.querySelector('.mag-btn');
  const stage = root.querySelector('.mag-stage');
  const CAP = 28;
  const STRENGTH = 0.3;
  let userActive = false, userTimer;

  const apply = (dx, dy) => {
    const mag = Math.hypot(dx, dy) || 1;
    const k = Math.min(1, CAP / (mag * STRENGTH));
    btn.style.transform = 'translate(' + (dx * STRENGTH * k) + 'px,' + (dy * STRENGTH * k) + 'px)';
  };

  stage.addEventListener('mousemove', (e) => {
    userActive = true;
    clearTimeout(userTimer);
    userTimer = setTimeout(() => { userActive = false; }, 1500);
    const r = btn.getBoundingClientRect();
    apply(e.clientX - (r.left + r.width / 2), e.clientY - (r.top + r.height / 2));
  });
  stage.addEventListener('mouseleave', () => { btn.style.transform = ''; });

  const io = new IntersectionObserver((entries) => {
    entries.forEach(en => {
      if (en.isIntersecting && !btn._auto) {
        btn._auto = true;
        const t0 = performance.now();
        const loop = (now) => {
          if (!userActive) {
            const t = (now - t0) / 1000;
            const a = 110;
            const s = Math.sin(t * 0.7);
            const c = Math.cos(t * 0.7);
            const dx = (a * c) / (1 + s * s);
            const dy = (a * s * c) / (1 + s * s);
            apply(dx, dy);
          }
          if (btn._auto) requestAnimationFrame(loop);
        };
        requestAnimationFrame(loop);
      }
    });
  }, { threshold: 0.2 });
  io.observe(stage);
}
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: cta-magnetic-button-minimal -->
<section class="mag-block">
  <p class="mag-eyebrow">Magnetisch</p>
  <h2 class="mag-headline">Een knop die<br/>weet dat je kijkt.</h2>
  <hr class="mag-rule" />
  <div class="mag-stage">
    <button class="mag-btn" type="button">
      <span class="mag-label">Start een project</span>
      <span class="mag-arrow">→</span>
    </button>
  </div>
  <p class="mag-foot">Beweging volgt aandacht. Niets meer.</p>
</section>
3. Styling-template — verplicht eigen invulling per merk
/* Styling: cta-magnetic-button-minimal — cream/ink, Inter 300, single 1px rule */
.mag-block {
  --paper: #F4F1EB;
  --ink: #0A0A0A;
  --rule: rgba(10,10,10,.18);
  background: var(--paper);
  color: var(--ink);
  font-family: 'Inter', system-ui, sans-serif;
  font-weight: 300;
  display: grid;
  gap: 2rem;
  padding: clamp(2rem, 6vw, 4rem) 0;
  max-width: 64ch;
}
.mag-headline { font-size: clamp(1.8rem, 3.6vw, 2.6rem); line-height: 1.18; margin: 0; font-weight: 300; }
.mag-rule { border: 0; border-top: 1px solid var(--rule); margin: 0; }
.mag-stage { min-height: 220px; display: grid; place-items: center; }
.mag-btn {
  background: transparent;
  border: 1px solid var(--ink);
  color: var(--ink);
  padding: 1.1rem 2rem;
  font-family: inherit;
  font-weight: 300;
  font-size: 1rem;
  border-radius: 999px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: .7rem;
  will-change: transform;
}