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

Motion Lab / FAQ / svg morph toggle / playful

Veelgestelde vragen.

  • Meestal binnen 10 werkdagen. We werken in vaste sprints, jij weet wat je krijgt en wanneer.

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

  • Ja. Eenvoudige editor + uitlegvideo. Liever niet zelf? Wij doen het in een onderhoudspakket.

  • Zeker. Stuur logo, kleuren en fonts mee — wij vertalen het 1:1 naar een snelle site.

1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: faq-svg-morph-toggle-playful (techniek #33 — SVG morphing)
// Per item morphen we het "+" path naar "×" via GSAP attr-tween (path-d interpolation).
// Bouncy back.out(1.7), auto-loop opent items 1..4 met 1.2s tussenpoos, dan reset.
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-play');
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.45, ease: 'back.out(1.7)' });
    if (open) {
      const h = body.scrollHeight || 80;
      gsap.fromTo(body, { height: 0, opacity: 0 }, { height: h, opacity: 1, duration: 0.5, ease: 'back.out(1.4)', onComplete: () => { body.style.height = 'auto'; } });
    } else {
      gsap.to(body, { height: 0, opacity: 0, duration: 0.35, ease: 'power2.in' });
    }
  };
  items.forEach((it) => it.querySelector('.faq-trigger').addEventListener('click', () => setOpen(it, !it.classList.contains('is-open'))));
  // Auto-loop bij viewport-enter
  const start = () => {
    items.forEach((it, i) => setTimeout(() => setOpen(it, true), 400 + i * 1200));
    setTimeout(() => items.forEach((it) => setOpen(it, false)), 400 + items.length * 1200 + 1500);
  };
  start();
}
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: faq-svg-morph-toggle-playful -->
<section class="faq-morph-play">
  <h2>Veelgestelde vragen.</h2>
  <ul class="faq-list">
    <li data-faq class="faq-card">
      <button class="faq-trigger" type="button">
        <span class="faq-q">Vraag?</span>
        <svg class="faq-icon" viewBox="0 0 24 24" width="22" height="22"><path class="morph-icon" d="M12 5 L12 19 M5 12 L19 12" stroke="currentColor" stroke-width="2.5" 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-playful */
:root {
  --block-bg: #FFE3EE;
  --block-fg: #2A1530;
  --block-card: #FFFFFF;
  --block-mint: #A8E0BE;
  --block-sky: #B5E3FF;
}
.faq-morph-play { background: var(--block-bg); color: var(--block-fg); font-family: 'Quicksand', system-ui, sans-serif; padding: clamp(3rem,8vw,7rem) clamp(1.5rem,5vw,4rem); }
.faq-morph-play h2 { font-weight: 700; font-size: clamp(2.25rem,5vw,3.5rem); letter-spacing: -0.01em; margin: 0 0 2.5rem 0; }
.faq-morph-play .faq-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1rem; max-width: 720px; }
.faq-morph-play .faq-card { background: var(--block-card); border-radius: 24px; padding: 0.5rem 1.5rem; box-shadow: 0 6px 0 rgba(42,21,48,.08); transition: box-shadow .25s ease; }
.faq-morph-play .faq-card.is-open { box-shadow: 0 10px 0 var(--block-mint); }
.faq-morph-play .faq-trigger { width: 100%; appearance: none; background: transparent; border: 0; cursor: pointer; padding: 1.25rem 0; display: flex; justify-content: space-between; align-items: center; font: 600 1.05rem/1.3 'Quicksand', sans-serif; color: var(--block-fg); }
.faq-morph-play .faq-icon { background: var(--block-sky); border-radius: 999px; width: 36px; height: 36px; padding: 7px; flex: none; }
.faq-morph-play .faq-card.is-open .faq-icon { background: var(--block-mint); }
.faq-morph-play .faq-body { height: 0; opacity: 0; overflow: hidden; }
.faq-morph-play .faq-body p { margin: 0 0 1.25rem 0; font: 400 0.95rem/1.55 'Quicksand', sans-serif; color: rgba(42,21,48,.78); }
@media (prefers-reduced-motion: reduce) { .faq-morph-play .faq-body { height: auto; opacity: 1; } }