{
 "$schema": "../../schema.json",
 "schema": "fundus/1",
 "id": "css-view-reveal-nativ",
 "name": "Native CSS-Scroll-Reveals mit view()",
 "name_en": "CSS scroll-driven reveals (animation-timeline: view())",
 "kurz": "Inhalte gleiten an die Scrollposition gekoppelt herein, rein in CSS über animation-timeline: view(), ohne Library und ohne JS-Init.",
 "kategorie": "Scroll & Section-Animationen",
 "kategorie_slug": "scroll",
 "kategorie_nr": 5,
 "bau_chat": "C2",
 "prioritaet": "A",
 "trendstatus": "aufsteigend",
 "belegstatus": "belegt",
 "website_belege": 3,
 "stimmung": [
  "ruhig",
  "präzise"
 ],
 "stimmung_roh": [
  "technisch",
  "leicht",
  "universell"
 ],
 "technik": [
  "css",
  "scroll-driven-css"
 ],
 "technik_gruppen": [
  "css"
 ],
 "leistungskosten": "niedrig",
 "steckbrief": {
  "beschreibung": "Ein- und Ausblendeffekte hängen direkt an der Position des Elements im Viewport, ohne JavaScript. animation-timeline: view() erzeugt eine anonyme View-Timeline. animation-range legt fest, in welchem Abschnitt die Keyframes laufen: entry, exit, cover, contain, entry-crossing oder exit-crossing. Typisch tritt ein Element von entry 0% bis entry 100% ein (Deckkraft, Verschiebung, Skalierung, Clip), bleibt in contain ruhig und tritt in exit wieder aus. Mit view-timeline-name und timeline-scope steuert ein Element die Animation eines anderen.",
  "warum_es_wirkt": "Die Bewegung folgt dem Finger bzw. dem Mausrad, nicht einer Uhr. Sie läuft auf dem Compositor und bleibt ruckelfrei, auch wenn der Main-Thread voll ist. Keine Library, kein Layout-Sprung durch JS-Initialisierung. Rückwärts scrollen spielt den Effekt rückwärts.",
  "wann_einsetzen": "Für Standard-Reveals, Parallaxe, Fortschrittsbalken, schrumpfende Header und Karussell-Effekte. Überall, wo keine komplexe Abfolge mit Callbacks nötig ist.",
  "wann_nicht": "Für mehrstufige Sequenzen mit Callbacks, Physik oder Canvas-Steuerung (dafür GSAP). Nicht als einzige Darstellung: In Browsern ohne Support müssen Inhalte ohne Animation sichtbar sein. Nicht für Inhalte über dem Falz, sonst sind sie beim Laden halb ausgeblendet.",
  "varianten": [
   "Anonyme view()-Timeline pro Element",
   "Benannte Timeline (view-timeline-name) mit timeline-scope, damit ein Element ein anderes steuert",
   "scroll(root) für seitenweite Effekte wie eine Fortschrittsleiste",
   "Symmetrisch: rein in entry, raus in exit (zwei Animationen, die zweite mit fill forwards)",
   "Per JS: new ViewTimeline({ subject }) und element.animate(…, { timeline, rangeStart, rangeEnd })"
  ],
  "umsetzung": "@media (prefers-reduced-motion: no-preference) { @supports (animation-timeline: view()) { .in { animation: in linear both; animation-timeline: view(); animation-range: entry 25% cover 50%; } } } @keyframes in { from { opacity: 0; translate: -15px 0; } }. animation-timeline immer NACH dem animation-Shorthand setzen, der Shorthand setzt es auf auto zurück. Achtung Spezifität: eine stärkere Regel mit dem Shorthand löscht die Timeline ebenfalls. Dauer auf auto lassen. view() nimmt immer den nächsten Scroll-Container; ein Elternelement mit overflow: hidden wird zum falschen Scroller, darum overflow: clip verwenden. Stagger entsteht über versetzte Ranges. Browser: Chromium ab 115 und Safari 26; Firefox ist laut MDN eingeschränkt verfügbar, deshalb Fallback einplanen.",
  "mobil": "Ideal für Mobilgeräte, weil compositor-getrieben. Keine Sonderbehandlung nötig. Große seitliche Wege (-4rem) werden am Rand abgeschnitten, also body { overflow-x: clip } setzen.",
  "reduzierte_bewegung": "Die komplette Regel in @media (prefers-reduced-motion: no-preference) kapseln. Dann bleibt alles im Endzustand sichtbar.",
  "barrierefreiheit": "Den Startzustand (opacity 0) nur innerhalb von @supports setzen, damit Inhalte ohne Support sichtbar bleiben. Inhalte über dem Falz nicht verstecken. Rein dekorativ, keine Information darf nur in der Animation stecken."
 },
 "messwerte": {
  "easing": "linear bzw. ease-in/ease-out je Keyframe",
  "dauer_ms": "auto (scrollgekoppelt)",
  "stagger_ms": "über unterschiedliche animation-range-Offsets",
  "distanz": "translateX -15px (nerdy.dev), -4rem (una.im)",
  "sonstiges": "Ranges: entry 25% cover 50%; entry 0% cover 35%; 50% 75%",
  "herkunft": "beobachtet",
  "quelle": "https://nerdy.dev/style.css?__frsh_c=1xfeax0m04df"
 },
 "kurve": {
  "bezier": [
   0.42,
   0,
   1,
   1
  ],
  "css": "cubic-bezier(0.42, 0, 1, 1)",
  "name": "ease-in",
  "dauer_ms": null
 },
 "gesehen_bei": [
  {
   "seite": "nerdy.dev (Adam Argyle)",
   "url": "https://nerdy.dev/",
   "auszeichnung": "",
   "beobachtung": "Live geprüft (style.css): Listenpunkte animieren opacity 0 + translateX(-15px) über view-timeline mit animation-range entry 25% cover 50%; Medien \"promote-media\" mit animation-timeline:view(), Range entry 10% entry 80%, ease-out; nur bei prefers-reduced-motion:no-preference.",
   "art": "website"
  },
  {
   "seite": "Una Kravets (una.im)",
   "url": "https://una.im/",
   "auszeichnung": "",
   "beobachtung": "Live geprüft (index.CLoQw8CE.css): @supports (animation-timeline: view()){.links-link{animation:showup ease-in both;animation-timeline:view();animation-range:entry 0% cover 35%}}, @keyframes showup von opacity 0, translate -4rem 0.",
   "art": "website"
  },
  {
   "seite": "Kevin Powell",
   "url": "https://kevinpowell.co/",
   "auszeichnung": "",
   "beobachtung": "Live geprüft (style.BN4CjnzC.css): Unterstreichung wächst per animation linear both view() mit animation-range 50% 75% (background-size 0 % → voll, 8px hoch).",
   "art": "website"
  }
 ],
 "tutorials": [
  {
   "titel": "Chrome Developers – Scroll-driven Animations",
   "url": "https://developer.chrome.com/docs/css-ui/scroll-driven-animations"
  },
  {
   "titel": "View Timeline Ranges Visualizer",
   "url": "https://scroll-driven-animations.style/tools/view-timeline/ranges/"
  },
  {
   "titel": "MDN – animation-timeline",
   "url": "https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timeline"
  },
  {
   "titel": "scroll-driven-animations.style (Bramus)",
   "url": "https://scroll-driven-animations.style/"
  }
 ],
 "kombinierbar_mit": [
  "clip-path-bild-reveal",
  "mehrschicht-parallax",
  "scroll-fortschritt",
  "css-scroll-triggered"
 ],
 "urls": {
  "detail": "https://cecodesign.de/lab/fundus/scroll/css-view-reveal-nativ/",
  "meta": "https://cecodesign.de/lab/fundus/scroll/css-view-reveal-nativ/meta.json",
  "demo": "https://cecodesign.de/lab/fundus/scroll/css-view-reveal-nativ/demo"
 },
 "herkunft": {
  "quelldateien": [
   "scroll.json",
   "patch-belege-3.json"
  ],
  "pruefnotizen": [
   "patch-belege-3.json: Drei Produktionsbelege."
  ]
 },
 "bau": {
  "status": "fertig",
  "kurz": "Inhalte gleiten an die Scrollposition gekoppelt herein, rein in CSS über animation-timeline: view(), ohne Library und ohne JS-Init.",
  "demo": true,
  "abhaengigkeiten": [],
  "werte": {
   "timeline": "view() (anonym, pro Element); Index: view-timeline-name --a1 bis --a4 + timeline-scope am Abschnitt; Fortschrittsleiste: scroll(root)",
   "dauer_ms": "auto (scrollgekoppelt)",
   "variante_sanft": "opacity 0 → 1, translate -15px 0 → 0, linear, animation-range entry 25% cover 50% (nerdy.dev)",
   "variante_weit": "opacity 0 → 1, translate -4rem 0 → 0, ease-in cubic-bezier(0.42, 0, 1, 1), animation-range entry 0% cover 35% (una.im)",
   "variante_ein_aus": "rein: opacity 0, translate 0 2rem, scale .96 → normal, linear, entry 0% entry 100%; raus: zurück auf opacity 0, translate 0 -2rem, scale .96, linear, exit 0% exit 100% (fill forwards)",
   "stagger": "über versetzte Ranges: --v 0% / 4% / 8% wird auf Start und Ende der Range addiert (Kicker, Titel, Text)",
   "medien": "opacity 0 → 1, scale .92 → 1, ease-out, entry 10% entry 80%; Bild im Rahmen: scale 1.14, translate 0 -6% → 0 6%, linear, cover 0% cover 100%",
   "unterstreichung": "background-size 0% → 100% × .16em, linear, animation-range 50% 75% (cover), Akzent #d4522a",
   "index_aktiv": "Farbe rgba(25,24,22,.62) → #191816 und Strich scale .35 → 1 bei 12–88 %, animation-range cover 25% cover 75%",
   "fallback": "@supports not: einmalig per IntersectionObserver (threshold 0.2), opacity + translate, 900 ms cubic-bezier(0.16, 1, 0.3, 1)",
   "ui": "Umschalter 150 ms",
   "schriften": "Instrument Sans 600 (Titel), Instrument Serif Italic (Akzent), Geist Mono 11px versal 0,14em"
  },
  "ausloeser": "scroll",
  "touch": "Identisch zu Desktop; die Animationen hängen nur am Scroll und laufen auf dem Compositor. Unter 760 px wird der Index zur klebenden Nummernleiste (01 bis 04), die benannte Timelines weiter hervorheben. Hochformat-Bilder. Die Werte-Anzeige ist auf dem Telefon ein einklappbarer Chip unten rechts, standardmäßig zu.",
  "reduziert": "Alle Regeln liegen in @media (prefers-reduced-motion: no-preference) und :root:not(.reduziert). Bei reduzierter Bewegung ist alles sofort sichtbar, Unterstreichungen sind voll, die Fortschrittsleiste entfällt.",
  "korrekturen": {
   "beschreibung": "Ein- und Ausblendeffekte hängen direkt an der Position des Elements im Viewport, ohne JavaScript. animation-timeline: view() erzeugt eine anonyme View-Timeline. animation-range legt fest, in welchem Abschnitt die Keyframes laufen: entry, exit, cover, contain, entry-crossing oder exit-crossing. Typisch tritt ein Element von entry 0% bis entry 100% ein (Deckkraft, Verschiebung, Skalierung, Clip), bleibt in contain ruhig und tritt in exit wieder aus. Mit view-timeline-name und timeline-scope steuert ein Element die Animation eines anderen.",
   "warum_es_wirkt": "Die Bewegung folgt dem Finger bzw. dem Mausrad, nicht einer Uhr. Sie läuft auf dem Compositor und bleibt ruckelfrei, auch wenn der Main-Thread voll ist. Keine Library, kein Layout-Sprung durch JS-Initialisierung. Rückwärts scrollen spielt den Effekt rückwärts.",
   "wann_einsetzen": "Für Standard-Reveals, Parallaxe, Fortschrittsbalken, schrumpfende Header und Karussell-Effekte. Überall, wo keine komplexe Abfolge mit Callbacks nötig ist.",
   "wann_nicht": "Für mehrstufige Sequenzen mit Callbacks, Physik oder Canvas-Steuerung (dafür GSAP). Nicht als einzige Darstellung: In Browsern ohne Support müssen Inhalte ohne Animation sichtbar sein. Nicht für Inhalte über dem Falz, sonst sind sie beim Laden halb ausgeblendet.",
   "varianten": [
    "Anonyme view()-Timeline pro Element",
    "Benannte Timeline (view-timeline-name) mit timeline-scope, damit ein Element ein anderes steuert",
    "scroll(root) für seitenweite Effekte wie eine Fortschrittsleiste",
    "Symmetrisch: rein in entry, raus in exit (zwei Animationen, die zweite mit fill forwards)",
    "Per JS: new ViewTimeline({ subject }) und element.animate(…, { timeline, rangeStart, rangeEnd })"
   ],
   "umsetzung": "@media (prefers-reduced-motion: no-preference) { @supports (animation-timeline: view()) { .in { animation: in linear both; animation-timeline: view(); animation-range: entry 25% cover 50%; } } } @keyframes in { from { opacity: 0; translate: -15px 0; } }. animation-timeline immer NACH dem animation-Shorthand setzen, der Shorthand setzt es auf auto zurück. Achtung Spezifität: eine stärkere Regel mit dem Shorthand löscht die Timeline ebenfalls. Dauer auf auto lassen. view() nimmt immer den nächsten Scroll-Container; ein Elternelement mit overflow: hidden wird zum falschen Scroller, darum overflow: clip verwenden. Stagger entsteht über versetzte Ranges. Browser: Chromium ab 115 und Safari 26; Firefox ist laut MDN eingeschränkt verfügbar, deshalb Fallback einplanen.",
   "mobil": "Ideal für Mobilgeräte, weil compositor-getrieben. Keine Sonderbehandlung nötig. Große seitliche Wege (-4rem) werden am Rand abgeschnitten, also body { overflow-x: clip } setzen.",
   "reduzierte_bewegung": "Die komplette Regel in @media (prefers-reduced-motion: no-preference) kapseln. Dann bleibt alles im Endzustand sichtbar.",
   "barrierefreiheit": "Den Startzustand (opacity 0) nur innerhalb von @supports setzen, damit Inhalte ohne Support sichtbar bleiben. Inhalte über dem Falz nicht verstecken. Rein dekorativ, keine Information darf nur in der Animation stecken."
  },
  "geprueft": "01.11.2026",
  "notiz": "Studioseite „Büro Feldlinie“ (erfunden): Texte, Projektzeilen und Bilder gleiten rein per CSS herein. Umschalter für drei Varianten: Sanft (nerdy.dev), Weit (una.im), Ein + aus (symmetrisch mit exit). Dazu eine wachsende Unterstreichung (Range 50% 75%, wie kevinpowell.co), Bild-Parallaxe im Rahmen mit overflow: clip, eine Fortschrittsleiste mit scroll(root) und ein klebender Index, den die Projektzeilen über benannte Timelines und timeline-scope hervorheben. Ohne Support blendet ein IntersectionObserver einmalig ein. Die Vorschau scrollt selbst und wechselt jede Runde die Variante. Korrektur 01.11.2026: Auf dem Telefon lag die feste Werte-Leiste über dem Fließtext. Unter 760 px ist sie jetzt ein einklappbarer Chip „Werte +“ unten rechts (eingeklappt 32 px hoch), Tippen klappt Range und Bewegung auf. Review 01.11.2026: In der Galerie-Vorschau lag die Werte-Box über den Abschnittslabels; sie ist dort jetzt ausgeblendet. Bilder 01.11.2026: eigene Bilder statt Portfolio. Zugang 02.11.2026: --leise Alpha .55 → .66 (5,3:1); inaktive Einträge im Arbeiten-Index .38 → .62 (4,6:1), aktiv bleibt #191816 mit Strich; Kontrastmodus-Rahmen für Knöpfe."
 }
}
