← Zurück
Web · 2026

move.

Rolle: Technische Umsetzung (IPA-Abschlussarbeit)

move.

Für meine IPA (Individuelle Praktische Arbeit) als Mediamatiker habe ich die Website des Move-Camps komplett neu aufgebaut — ein 12-tägiges Einzelprojekt für das Missionswerk msd. Design kam vom internen Grafikteam, Inhalte vom Move-Team, meine Aufgabe war die vollständige technische Umsetzung: WordPress + Divi 5, mit ACF, FluentForms, GSAP und dynamischen Inhalten über die WordPress-REST-API.

Dynamische Popups statt Divi-Loop

Einsätze und Action Steps werden nicht serverseitig mit Divi gerendert, sondern beim Laden der Seite per REST API im Hintergrund vorgeladen und in einem Cache-Objekt gehalten. Klickt jemand auf eine Karte, öffnet sich das Popup sofort mit den gecachten Daten — nur falls nötig wird nachgeladen.

const einsatzCache = {};

const alleVorladen = async () => {
  const response = await fetch(`/wp-json/wp/v2/einsatz?acf_format=standard`);
  const alle = await response.json();
  alle.forEach(m => { einsatzCache[m.id] = m; });
};
alleVorladen(); // im Hintergrund starten

karte.addEventListener('click', async function() {
  const id = this.dataset.id;
  let data = einsatzCache[id];
  if (!data) {
    data = await (await fetch(`/wp-json/wp/v2/einsatz/${id}?acf_format=standard`)).json();
  }
  // Popup mit data.acf befüllen …
});

iOS Safari setzt den Popup-Inhalt zurück

Beim Testen auf iOS Safari passierten zwei Dinge gleichzeitig: Die Tab-Leiste blendet sich beim Scrollen ein/aus und verändert dabei die sichtbare Viewport-Höhe — das Fullscreen-Popup sprang dadurch sichtbar. Behoben mit der CSS-Einheit dvh statt vh. Zusätzlich hat Safari bei diesem Resize teilweise den Popup-Inhalt auf die Platzhalter zurückgesetzt — gelöst mit einem MutationObserver, der den Inhalt überwacht und ihn bei Bedarf automatisch wieder aus dem Cache befüllt.

const observer = new MutationObserver(function() {
  if (popup.classList.contains('aktiv')) {
    const aktivesId = popup.dataset.aktivesId;
    const data = einsatzCache[aktivesId];
    if (data && q('#name').innerHTML !== data.title.rendered) {
      // Safari hat den DOM zurückgesetzt — Felder neu befüllen
      q('#name').innerHTML = data.title.rendered;
      q('#beschreibung span').innerHTML = data.acf.beschreibung;
      // …
    }
  }
});
observer.observe(inhalt, { childList: true, subtree: true, characterData: true });

Video stoppen, obwohl Divi den Klick blockiert

Beim Schliessen eines Video-Popups lief das YouTube-Video im Hintergrund weiter. Ein direkter Click-Listener auf den Schliessen-Button ging nicht, weil Divis eigene Interaktionslogik die Event-Propagation unterbricht. Stattdessen einfach die CSS-Klasse beobachtet, die Divi selbst beim Öffnen/Schliessen setzt:

window.addEventListener('load', function() {
  setTimeout(function() {
    document.querySelectorAll('.video_popup').forEach(function(popup) {
      const iframe = popup.querySelector('iframe');
      const originalSrc = iframe ? iframe.src.replace('?autoplay=1&', '?') : null;

      const observer = new MutationObserver(function() {
        if (!popup.classList.contains('aktiv')) {
          if (iframe) iframe.src = ''; // Popup zu -> Video stoppen
        } else {
          if (iframe && originalSrc) iframe.src = originalSrc; // Popup auf -> Video zurück
        }
      });
      observer.observe(popup, { attributes: true, attributeFilter: ['class'] });
    });
  }, 1000);
});

Animation der fliegenden Objekte

Auf der Startseite fliegen in unregelmässigen Abständen Bilder über den Screen. Umgesetzt mit GSAP statt reinem CSS, weil GSAP präzise Kontrolle über Zufallswerte, Timing und automatisches Entfernen der Elemente nach der Animation bietet.

function fly() {
  const o = imgs[Math.floor(Math.random() * imgs.length)];
  const sy = Math.random() * (window.innerHeight - o.w);
  const img = document.createElement('img');
  img.src = o.src;
  document.body.appendChild(img);
  gsap.set(img, { x: -(o.w + 30), y: sy });
  gsap.to(img, {
    x: window.innerWidth + o.w + 30,
    y: sy + (Math.random() - .5) * window.innerHeight * .6,
    rotation: (360 + Math.random() * 360) * (Math.random() < .5 ? 1 : -1),
    duration: 4 + Math.random() * 3,
    ease: 'none',
    onComplete: () => img.remove()
  });
}

function schedule() {
  setTimeout(() => { fly(); schedule(); }, (5 + Math.random() * 10) * 1000);
}
schedule();
Live ansehen

Kategorie

Web

Jahr

2026

Rolle

Technische Umsetzung (IPA-Abschlussarbeit)

© 2026 Yanis Dängeli