← Blurr Motion faq-svg-morph-toggle-minimal
Categorie faq Tier 2 Techniek #33 Deps gsap

Motion Lab / FAQ / svg morph toggle / minimal

Vragen.

  • Doorgaans binnen tien werkdagen. Vaste sprints, geen ruis.

  • Vanaf 2.450 euro, alles inbegrepen. Hosting blijft gratis via Cloudflare.

  • Ja. Lichtgewicht editor, korte uitleg. Of laat ons het beheer doen.

  • Stuur logo, kleuren en fonts mee — wij vertalen het 1:1 naar code.

1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: faq-svg-morph-toggle-minimal (techniek #33 — SVG morphing)
// Per item morphen we het "+" path naar "×" via GSAP attr-tween (path-d interpolation).
// Restrained ease 'power2.inOut', auto-loop opent items 1..4 met 1.4s tussenpoos.
import gsap from 'https://esm.sh/[email protected]';
const PLUS  = 'M12 5 L12 19 M5 12 L19 12';
const CROSS = 'M6 6 L18 18 M18 6 L6 18';
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const root = document.querySelector('.faq-morph-min');
if (root) {
  const items = Array.from(root.querySelectorAll('[data-faq]'));
  const setOpen = (item, open) => {
    const path = item.querySelector('.morph-icon');
    const body = item.querySelector('.faq-body');
    item.classList.toggle('is-open', open);
    if (reduce) { path.setAttribute('d', open ? CROSS : PLUS); body.style.height='auto'; body.style.opacity='1'; return; }
    gsap.to(path, { attr: { d: open ? CROSS : PLUS }, duration: 0.5, ease: 'power2.inOut' });
    if (open) {
      const h = body.scrollHeight || 80;
      gsap.fromTo(body, { height: 0, opacity: 0 }, { height: h, opacity: 1, duration: 0.6, ease: 'power2.out', onComplete: () => { body.style.height = 'auto'; } });
    } else {
      gsap.to(body, { height: 0, opacity: 0, duration: 0.4, ease: 'power2.in' });
    }
  };
  items.forEach((it) => it.querySelector('.faq-trigger').addEventListener('click', () => setOpen(it, !it.classList.contains('is-open'))));
}
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: faq-svg-morph-toggle-minimal -->
<section class="faq-morph-min">
  <h2>Vragen.</h2>
  <ul class="faq-list">
    <li data-faq class="faq-row">
      <button class="faq-trigger" type="button">
        <span class="faq-q">Vraag?</span>
        <svg class="faq-icon" viewBox="0 0 24 24" width="20" height="20"><path class="morph-icon" d="M12 5 L12 19 M5 12 L19 12" stroke="currentColor" stroke-width="1" stroke-linecap="round" fill="none"/></svg>
      </button>
      <div class="faq-body"><p>Antwoord.</p></div>
    </li>
    <!-- 4 items totaal -->
  </ul>
</section>
3. Styling-template — verplicht eigen invulling per merk
/* Styling: faq-svg-morph-toggle-minimal */
:root {
  --block-paper: #F4F1EB;
  --block-ink: #0A0A0A;
  --block-rule: rgba(10,10,10,.14);
}
.faq-morph-min { background: var(--block-paper); color: var(--block-ink); font-family: 'Inter', system-ui, sans-serif; font-weight: 300; padding: clamp(4rem,10vw,9rem) clamp(1.5rem,5vw,4rem); }
.faq-morph-min h2 { font-weight: 300; font-size: clamp(2rem,4vw,3rem); letter-spacing: -0.02em; margin: 0 0 4rem 0; }
.faq-morph-min .faq-list { list-style: none; margin: 0; padding: 0; max-width: 720px; }
.faq-morph-min .faq-row { border-top: 1px solid var(--block-rule); }
.faq-morph-min .faq-row:last-child { border-bottom: 1px solid var(--block-rule); }
.faq-morph-min .faq-trigger { width: 100%; appearance: none; background: transparent; border: 0; cursor: pointer; padding: 1.75rem 0; display: flex; justify-content: space-between; align-items: center; font: 300 1rem/1.4 'Inter', sans-serif; color: inherit; text-align: left; }
.faq-morph-min .faq-icon { flex: none; }
.faq-morph-min .faq-body { height: 0; opacity: 0; overflow: hidden; }
.faq-morph-min .faq-body p { margin: 0 0 1.75rem 0; font: 300 0.95rem/1.65 'Inter', sans-serif; color: rgba(10,10,10,.62); max-width: 56ch; }