{
 "$schema": "../../schema.json",
 "schema": "fundus/1",
 "id": "parallel-crossfade-preload",
 "name": "Paralleler Übergang mit Vorladen (beide Seiten gleichzeitig im DOM)",
 "name_en": "Parallel / sync page transition with preloading",
 "kurz": "Raus-Animation und Laden der neuen Seite starten beim Klick gleichzeitig, beide Container liegen kurz übereinander im DOM: Die Ladezeit verschwindet hinter dem Übergang.",
 "kategorie": "Seitenübergänge",
 "kategorie_slug": "seitenuebergaenge",
 "kategorie_nr": 1,
 "bau_chat": "C3",
 "prioritaet": "B",
 "trendstatus": "aufsteigend",
 "belegstatus": "teilbelegt",
 "website_belege": 1,
 "stimmung": [
  "ruhig",
  "präzise"
 ],
 "stimmung_roh": [
  "flüssig",
  "technisch",
  "unaufdringlich"
 ],
 "technik": [
  "vanilla-js",
  "gsap",
  "history-api",
  "react-router",
  "view-transitions"
 ],
 "technik_gruppen": [
  "css",
  "js",
  "gsap",
  "andere"
 ],
 "leistungskosten": "mittel",
 "steckbrief": {
  "beschreibung": "Beim Klick starten zwei Dinge gleichzeitig: die Raus-Animation der alten Seite und das Laden der neuen (HTML, JS-Chunk, Titelbild). Die neue Seite wird neben der alten in denselben Bereich eingehängt. Beide animieren überlappend: alt raus mit Fade und y-Versatz, neu rein. Erst wenn Animation und Laden fertig sind, wird der alte Container entfernt. So entsteht keine Totzeit zwischen Raus und Rein.",
  "warum_es_wirkt": "Der Übergang verdeckt die Ladezeit, statt sie zu verlängern. Die Seite wirkt dadurch schneller. Überlappung liest sich flüssiger als das klassische Raus, Warten, Rein.",
  "wann_einsetzen": "Bei JS-Routern (Barba mit sync: true, Taxi, eigener Router, React Router), wenn Seiten spürbar laden, zum Beispiel wegen großer Titelbilder oder Daten.",
  "wann_nicht": "Wenn eine klassische Website mit mehreren Seiten reicht: Dort blendet der Browser mit @view-transition { navigation: auto; } selbst über. Auch nicht, wenn die Seiten ohnehin sofort da sind.",
  "varianten": [
   "Eigener Vanilla-Router: Container klonen, neue Seite einsetzen, beide animieren (blenkcode, Codrops)",
   "React Router mit startViewTransition und flushSync, am Ende ein Clip-Morph per View Transition (Thibault Guignand)",
   "Barba mit sync: true",
   "Taxi mit enableViewTransitions: Der DOM-Tausch läuft in document.startViewTransition"
  ],
  "umsetzung": "Im Klick-Handler preventDefault, dann laden und Raus-Animation gleichzeitig starten: const seite = cache.get(url) ?? fetch(url).then(r => r.text()); const weg = raus(alt); Sobald die Seite da ist, den Container per DOMParser herauslösen, neben den alten einsetzen und rein(neu) starten. Mit await Promise.all([weg, reinFertig]) auf beide warten, dann den alten Container entfernen, history.pushState und document.title setzen. Vorladen per Hover, Fokus oder pointerdown in eine Map von Promises legen. React: navigate in flushSync innerhalb von startViewTransition aufrufen, sonst fotografiert der Browser zweimal die alte Seite. Safari: 50 ms Puffer und void el.offsetHeight vor dem Entfernen (Clip-Path-Cache). Fallstricke: Doppelklicks sperren, Scroll-Position und Fokus selbst setzen, Seitenaufruf für die Statistik manuell senden.",
  "mobil": "Übergang unverändert. Statt bei Hover lädt die Seite beim Aufsetzen des Fingers (touchstart oder pointerdown) oder sobald der Link sichtbar wird.",
  "reduzierte_bewegung": "Sofortiger Tausch ohne Animation, sobald die Seite geladen ist. Taxi macht das bei reduzierter Bewegung automatisch.",
  "barrierefreiheit": "Bei JS-Routern Pflicht: Fokus auf die neue Überschrift setzen, den Seitentitel per aria-live ansagen, Scroll-Position zurücksetzen und den Zurück-Button testen. Den alten Container während des Übergangs inert schalten."
 },
 "messwerte": {
  "easing": "power2.inOut",
  "dauer_ms": "300 (UI-Ebenen), 350 (Content ab 250 ms), gesamt etwa 600",
  "stagger_ms": "",
  "distanz": "",
  "sonstiges": "Safari-Workaround 50 ms Puffer",
  "herkunft": "beobachtet",
  "quelle": "https://blog.goodlaptops.com/programming/from-shader-uniforms-to-clip-path-wipes-how-gsap-drives-my-portfolio"
 },
 "kurve": {
  "bezier": [
   0.65,
   0,
   0.35,
   1
  ],
  "css": "cubic-bezier(0.65, 0, 0.35, 1)",
  "name": "power2.inOut",
  "dauer_ms": 300
 },
 "gesehen_bei": [
  {
   "seite": "Thibault Guignand Portfolio",
   "url": "https://www.thibaultguignand.com/",
   "auszeichnung": "",
   "beobachtung": "Linkklick startet Fade-out und Preload gleichzeitig: Route-Chunk und Hero-Bild laden, bevor GSAP malt. WebGL-Hintergrund, Grid, Seitentexte und Cursor gehen in 0,3 s mit power2.inOut auf opacity 0, der Content in 0,35 s ab 0,25 s. Die Timeline dauert etwa 0,6 s und wartet dann auf die Preloads. Preload-Strategie: Fonts sofort, Hero-Bild bei Hover, erstes Projekt während des Intros, alles andere später.",
   "art": "website",
   "browser_gemessen": true
  },
  {
   "seite": "Codrops-Demo: Async Page Transitions (blenkcode)",
   "url": "https://framework-ten-lilac.vercel.app/",
   "auszeichnung": "",
   "beobachtung": "Laut Repo-README: eigener Router auf History API, Transition-Registry für Seite-zu-Seite-Paare und Web Components für persistente UI. Der Container wird geklont, die neue Seite eingefügt und beide gleichzeitig animiert.",
   "art": "demo"
  }
 ],
 "tutorials": [
  {
   "titel": "Building Async Page Transitions in Vanilla JavaScript (Codrops, 02/2026)",
   "url": "https://tympanus.net/codrops/2026/02/26/building-async-page-transitions-in-vanilla-javascript/"
  },
  {
   "titel": "Building Parallel Page Transitions with GSAP and React Router (Codrops, 10/2026)",
   "url": "https://tympanus.net/codrops/2026/10/08/building-parallel-page-transitions-with-gsap-and-react-router/"
  },
  {
   "titel": "Taxi.js: View Transitions",
   "url": "https://taxi.js.org/view-transitions/"
  },
  {
   "titel": "codrops-demo Repository (blenkcode)",
   "url": "https://github.com/blenkcode/codrops-demo"
  }
 ],
 "kombinierbar_mit": [
  "clip-path-reveal-seite",
  "vt-shared-element-morph",
  "seitenwechsel-kamerafahrt"
 ],
 "urls": {
  "detail": "https://cecodesign.de/lab/fundus/seitenuebergaenge/parallel-crossfade-preload/",
  "meta": "https://cecodesign.de/lab/fundus/seitenuebergaenge/parallel-crossfade-preload/meta.json",
  "demo": "https://cecodesign.de/lab/fundus/seitenuebergaenge/parallel-crossfade-preload/demo"
 },
 "herkunft": {
  "quelldateien": [
   "uebergaenge.json",
   "patch-belege-3.json"
  ],
  "pruefnotizen": [
   "patch-belege-3.json: Kein weiterer Beleg; Thibault Guignand bleibt einziger Produktionsbeleg (bereits art website)."
  ]
 },
 "bau": {
  "status": "fertig",
  "kurz": "Raus-Animation und Laden der neuen Seite starten beim Klick gleichzeitig, beide Container liegen kurz übereinander im DOM: Die Ladezeit verschwindet hinter dem Übergang.",
  "demo": true,
  "abhaengigkeiten": [],
  "werte": {
   "easing": "power2.inOut = cubic-bezier(0.65, 0, 0.35, 1) (gemessen)",
   "ui_raus_ms": 300,
   "inhalt_raus_ms": 350,
   "inhalt_raus_ab_ms": 250,
   "raus_gesamt_ms": 600,
   "rein_ms": 350,
   "rein_start": "max(250 ms, Seite geladen)",
   "y_versatz_px": "16 (raus nach oben, rein von unten; eigene Wahl)",
   "puffer_vor_entfernen_ms": "50 (Safari-Workaround), danach void el.offsetHeight",
   "ladezeit_kalt_ms": "520 (simuliert: HTML plus Titelbild)",
   "vorladen": "pointerover (Maus), pointerdown (Touch), focusin (Tastatur); Cache als Map von Promises",
   "gesamt_parallel_ms": "etwa 870 kalt, 600 vorgeladen",
   "variante_nacheinander": "raus 600 ms, dann laden 520 ms, dann rein 350 ms, etwa 1470 ms",
   "variante_view_transition": "laden, dann document.startViewTransition(swap); ::view-transition-old 350 ms, ::view-transition-new 350 ms ab 250 ms, y 16 px, Gruppe ohne Animation",
   "hover_ui": "120 ms cubic-bezier(0.165, 0.84, 0.44, 1)",
   "schriften": "Host Grotesk 400/500, Newsreader Italic (Akzent), Geist Mono (Metadaten)",
   "farben": "#fcfcfc Grund, #0b0b09 Text, #646464 Sekundär, #e8e8e8 Linien, #f0f0f0 Hover, #2f5bea Fokus und Vorlade-Zustand"
  },
  "ausloeser": "klick",
  "touch": "Vorladen startet beim Aufsetzen des Fingers (pointerdown), der Übergang ist identisch. Werkbank einspaltig, Projektart in der Liste ausgeblendet.",
  "reduziert": "Kein Raus, kein Rein: Sobald die Seite geladen ist (oder aus dem Vorlade-Speicher kommt), wird der Container sofort getauscht. Die Zeitleiste zeigt nur das Laden.",
  "korrekturen": {
   "beschreibung": "Beim Klick starten zwei Dinge gleichzeitig: die Raus-Animation der alten Seite und das Laden der neuen (HTML, JS-Chunk, Titelbild). Die neue Seite wird neben der alten in denselben Bereich eingehängt. Beide animieren überlappend: alt raus mit Fade und y-Versatz, neu rein. Erst wenn Animation und Laden fertig sind, wird der alte Container entfernt. So entsteht keine Totzeit zwischen Raus und Rein.",
   "warum_es_wirkt": "Der Übergang verdeckt die Ladezeit, statt sie zu verlängern. Die Seite wirkt dadurch schneller. Überlappung liest sich flüssiger als das klassische Raus, Warten, Rein.",
   "wann_einsetzen": "Bei JS-Routern (Barba mit sync: true, Taxi, eigener Router, React Router), wenn Seiten spürbar laden, zum Beispiel wegen großer Titelbilder oder Daten.",
   "wann_nicht": "Wenn eine klassische Website mit mehreren Seiten reicht: Dort blendet der Browser mit @view-transition { navigation: auto; } selbst über. Auch nicht, wenn die Seiten ohnehin sofort da sind.",
   "varianten": [
    "Eigener Vanilla-Router: Container klonen, neue Seite einsetzen, beide animieren (blenkcode, Codrops)",
    "React Router mit startViewTransition und flushSync, am Ende ein Clip-Morph per View Transition (Thibault Guignand)",
    "Barba mit sync: true",
    "Taxi mit enableViewTransitions: Der DOM-Tausch läuft in document.startViewTransition"
   ],
   "umsetzung": "Im Klick-Handler preventDefault, dann laden und Raus-Animation gleichzeitig starten: const seite = cache.get(url) ?? fetch(url).then(r => r.text()); const weg = raus(alt); Sobald die Seite da ist, den Container per DOMParser herauslösen, neben den alten einsetzen und rein(neu) starten. Mit await Promise.all([weg, reinFertig]) auf beide warten, dann den alten Container entfernen, history.pushState und document.title setzen. Vorladen per Hover, Fokus oder pointerdown in eine Map von Promises legen. React: navigate in flushSync innerhalb von startViewTransition aufrufen, sonst fotografiert der Browser zweimal die alte Seite. Safari: 50 ms Puffer und void el.offsetHeight vor dem Entfernen (Clip-Path-Cache). Fallstricke: Doppelklicks sperren, Scroll-Position und Fokus selbst setzen, Seitenaufruf für die Statistik manuell senden.",
   "mobil": "Übergang unverändert. Statt bei Hover lädt die Seite beim Aufsetzen des Fingers (touchstart oder pointerdown) oder sobald der Link sichtbar wird.",
   "reduzierte_bewegung": "Sofortiger Tausch ohne Animation, sobald die Seite geladen ist. Taxi macht das bei reduzierter Bewegung automatisch.",
   "barrierefreiheit": "Bei JS-Routern Pflicht: Fokus auf die neue Überschrift setzen, den Seitentitel per aria-live ansagen, Scroll-Position zurücksetzen und den Zurück-Button testen. Den alten Container während des Übergangs inert schalten."
  },
  "geprueft": "01.11.2026",
  "notiz": "Welt: Portfolio der erfundenen Interaction-Designerin Merle Asmus aus Erfurt (Rezept freelancer-stille-visitenkarte: Host Grotesk, Newsreader kursiv, Graustufen plus #2f5bea). Die Startseite ist reiner Text, die Projektseiten haben eigene SVG-Titelbilder. Ein simulierter Server liefert HTML-Text nach 520 ms, den die Demo wie bei fetch mit DOMParser zerlegt. Der Vorlade-Punkt an jedem Link zeigt den Cache-Zustand. Die Zeitleiste zeigt live Raus, Laden, Rein und die Phase mit zwei Containern im DOM. Varianten: Parallel (das Muster), Nacheinander (Vergleich mit Totzeit), View Transition (Tausch nach dem Laden wie bei Taxi). Vorführung: ein kalter Klick (Laden läuft hinter dem Raus), dann zwei vorgeladene Klicks (echter Crossfade). history.pushState ist in der Demo bewusst weggelassen, weil sie in einem iframe läuft; im echten Einsatz gehört es dazu. Nachgebessert: Kunde Gärtnerei Lindenhof heißt jetzt Gärtnerei Wiesengrund (Lindenhof kommt in einer anderen Demo vor). AD-Review 01.11.2026: Note 4"
 }
}
