React Collapsing Header: iOS Large Title und Foto-Hero

Ein React Collapsing Header ist die Leiste oben auf dem Smartphone-Screen, die sich beim Scrollen verändert: der große iOS-Titel, der nach oben gleitet und an einen kleinen übergibt, oder das randlose Foto, das auf einer Inseratsseite zu einer Leiste aus Milchglas schrumpft. Die schnelle Lösung schreibt scrollY aus einem Scroll-Listener in den State und animiert die Höhe des Headers. Auf dem Smartphone ruckelt das. Dieses Tutorial baut beide Muster mit Motion Values für den Scroll, zuerst von Hand und dann mit dem Large Title Header und dem Hero Header von Wingo UI.
Welchen Collapsing Header braucht dein Screen?
Es gibt zwei Muster, und sie gehören auf unterschiedliche Screens: der Large Title auf Listen der obersten Ebene, der Foto-Hero auf Detailseiten. Android-Entwickler kennen das zweite als CollapsingToolbarLayout (opens in a new tab), das Material mit einklappendem Titel, Scrim und Parallax-Kindelementen dokumentiert. Eine Collapsing Toolbar im Web baut dieselbe Idee mit Styles nach, die an den Scroll gekoppelt sind.
Apples SwiftUI-Doku zum Large-Title-Modus (opens in a new tab) beschreibt das erste Muster: Der Titel beginnt unter der Toolbar und wandert in ihre Mitte, während der Inhalt scrollt. Im Web liefert Ionic denselben Effekt als einklappbare große Titel, und die Doku zum Titel (opens in a new tab) weist darauf hin, dass die Funktion nur unter iOS verfügbar ist.
Warum ruckelt der Header mit einem Scroll-Listener und useState?
Weil React dann an jedem einzelnen Frame beteiligt ist. Ein typisches React-Snippet für „Header beim Scrollen verkleinern“ sieht so aus:
Zwei Dinge gehen schief. Jedes Scroll-Event rendert die Komponente und alles darunter. Und height ist eine Layout-Eigenschaft, also berechnet der Browser in jedem Frame das Layout der ganzen Seite unter dem Header neu. Der Animations-Guide von web.dev (opens in a new tab) rät, transform und opacity zu animieren und Eigenschaften zu meiden, die Layout oder Paint auslösen. Das andere verbreitete Rezept, ein Boolean, das bei 50px umspringt, rendert seltener. Dafür springt der Header zwischen zwei Zuständen, statt dem Finger zu folgen.
Die Lösung: Halte den Offset außerhalb des React-States. Ein Motion Value aus motion/react kann sich 60- oder 120-mal pro Sekunde ändern und direkt in style schreiben, ohne Render. Booleans wie collapsed bleiben im State, und sie ändern sich nur, wenn sie wirklich umspringen.
Wie steuerst du einen Header über den Scroll, ohne neu zu rendern?
Lies den Offset als Motion Value und leite jeden Style mit useTransform davon ab. Das useScroll von motion liefert scrollY als Motion Value. Der Hook useScrollState von Wingo UI macht dasselbe für das Fenster oder einen beliebigen Container. Dazu kommen progress, ein hideProgress für Leisten, die wegfahren, und die Flags scrolled, hidden und atEnd, die nur dann neu rendern, wenn sie sich ändern.
Hier ist eine minimale iOS Large Title React-Komponente. Sie nutzt useScroll von motion und braucht deshalb nichts außer dem Paket motion. Wenn die Seite in einem inneren Container scrollt, setzt du stattdessen useScrollState().offset ein:
Hier ändert sich keine Höhe. Das große h1 ist ein normaler Block in der Seite, der Browser scrollt es also ohne Zusatzaufwand mit. Der Code blendet es nur aus. Die Leiste hat eine feste Höhe von 44px und blendet ihre Glasfläche ein. Die kompakte Kopie ist aria-hidden, damit Screenreader den Titel nur einmal vorlesen.
Die Schwachstelle sind die fest eingetragenen Zahlen. Ein Titel, der auf zwei Zeilen umbricht, ein Untertitel oder eine größere Schrifteinstellung verschieben den Punkt, an dem der Titel unter der Leiste durchläuft, und die Übergabe verrutscht. Die Komponente im nächsten Abschnitt misst diesen Punkt mit einem ResizeObserver (die Unterkante des Titels minus die Höhe der Leiste) und legt ihn ebenfalls in einem Motion Value ab. So bleiben die Transforms korrekt, ohne dass etwas rendert. Außerdem startet sie ausgeklappt, bis die erste Messung da ist. Das Server-HTML zeigt also nie einen halb ausgeblendeten Titel.
Vielleicht liest du auch den Vorschlag, dafür CSS Scroll-driven Animations zu nehmen. animation-timeline: scroll() läuft laut caniuse (opens in a new tab) in Chrome 115 und Safari 26, und MDN (opens in a new tab) führt es im Oktober 2026 noch als eingeschränkt verfügbar. Ein Ausblenden über einen festen Bereich klappt damit gut. Den Titel messen, einen halb eingeklappten Header einrasten lassen oder ein Suchfeld unter die Leiste schieben kann es nicht. Wir nutzen es deshalb höchstens als Verbesserung obendrauf.
Wie baust du die Übergabe beim iOS Large Title in React?
Installier die Komponente (sie ist Pro und bringt useScrollState sowie die Komponenten Button und Input mit):
Dann renderst du sie oben auf der Seite, vor dem Inhalt:
Das Root-Element des Headers hat display: contents. Die Sticky-Leiste gehört damit zur Seite und bleibt bis ganz unten angeheftet, statt sich zu lösen, sobald ihr eigener Wrapper wegscrollt. Die Übergabe passiert über 16px Scrollweg rund um die gemessene Kante: Der kompakte Titel steigt 4px in die Leiste, während die Glasfläche und die feine Trennlinie einblenden. Scroll ihn im Smartphone-Rahmen durch:
$ npx wingo-ui@latest add large-title-headerWas passiert, wenn jemand auf halbem Weg aufhört?
Mit snap (standardmäßig an) rastet ein Scroll, der 140ms lang zwischen ausgeklappt und eingeklappt stehen bleibt, am näheren Ende ein. Der Titel bleibt also nie halb unter der Leiste stehen. Die Prüfung wartet, solange noch ein Finger auf dem Screen liegt, und eine Seite, die zu kurz zum Einklappen ist, rastet wieder ausgeklappt ein.
Und wenn du über den oberen Rand hinausziehst?
Unter iOS meldet der Scroller während des Bounce einen negativen Offset. stretch skaliert den großen Titel dann von seiner Ecke aus, um bis zu 8% nach 48px Overscroll, so wie es der native Titel macht. Browser ohne Bounce melden nie einen negativen Offset, dort passiert also nichts.
Welchen Modus nimmt welcher Screen?
Nimm collapse für Listen der obersten Ebene und compact für Detailscreens, die per Navigation geöffnet werden. mode="collapse" ist die Übergabe von oben. mode="large" heftet den Titel in voller Größe an und klappt nie ein. mode="compact" rendert nur die Leiste, mit dem Titel schon darin, und passt zu genau so einem Detailscreen. titleSize="md" verkleinert den großen Titel von 34px auf 28px.
Wie baust du einen Parallax-Hero-Header fürs Smartphone?
Leg ein randloses Foto unter die Statusleiste, setz runde Buttons darauf und bewege das Foto langsamer als die Seite, bis eine Leiste übernimmt. Diesen Parallax Hero Header nutzen mobile Apps auf Inserats- und Produktseiten. Installier den Hero Header:
Die Parallaxe ist ein einziges Transform. Mit dem Standardwert parallax={0.5} bewegt sich das Foto um den halben Scroll-Offset nach unten, während die Seite darüber nach oben fährt. Es scheint also mit halber Geschwindigkeit zu scrollen. So sieht das Transform in hero-header.tsx aus, wobei drift der begrenzte Prop parallax ist und mediaHeight die gemessene Höhe des Heros:
Die Funktionsform von useTransform abonniert jeden Motion Value, den sie liest. Deshalb liest die Komponente mediaHeight, bevor sie verzweigt: Ein Wert, der nur in einem Zweig gelesen wird, der beim ersten Durchlauf nicht lief, würde das Transform nie aktualisieren.
Beim Overscroll unter iOS hält das Foto seine Oberkante fest und skaliert hoch, um die Lücke zu füllen, statt den Seitenhintergrund zu zeigen. Außerdem wird das Foto beim Verlassen um bis zu 45% abgedunkelt (fade), und ein Verlauf oben (scrim) hält Statusleiste und weiße Icons auch auf hellen Fotos lesbar. Auf den letzten 32px, bevor die Unterkante des Fotos die Leiste erreicht, blendet die Glasleiste ein, der kompakte Titel steigt hinein, und die runden Glas-Buttons werden zu getönten Buttons in der Leiste.
$ npx wingo-ui@latest add hero-headerDer Prop title ist hier der Text für die Leiste und ist aria-hidden. Darum rendert die Seite ihr eigenes h1 unter dem Hero. height ist standardmäßig 320px und auf 70dvh begrenzt, ein hohes Foto füllt also nie ein kleines Smartphone aus. Nimm onCollapsedChange, wenn die Farbe der Statusleiste der Übergabe folgen soll.
Wie funktioniert das in einem Scroll-Container oder einer PWA?
Beide Header folgen standardmäßig dem Fenster. Wenn eine App-Shell stattdessen ein inneres Element scrollt, gib dieses Element einmal mit ScrollContainerProvider nach unten weiter. Halte es über eine Callback-Ref im State, damit die Kindkomponenten nach dem Mounten aktualisiert werden:
Jeder Header, jede Tab-Leiste und jeder Back-to-top-Button darin liest jetzt dieses Element. Für Einzelfälle nehmen beide Header auch einen Prop scrollTarget.
In einer eigenständigen PWA darf die Notch die Leiste nicht verdecken. safeArea (standardmäßig an) setzt env(safe-area-inset-top) als Abstand über die Leiste. Im Desktop-Browser ist dieser Wert 0, und er setzt viewport-fit=cover in deinem Viewport-Meta-Tag voraus. Wie du das einrichtest, steht in unserem Guide zu Safe Area Insets mit Tailwind. Wenn die Seite auf bg-surface statt auf bg-background liegt, setz [--bar-bg:var(--surface)], damit das Glas passt. Ein solcher Header passt gut zu einer Navigationsleiste unten, wie sie das Tutorial zur React Bottom Navigation Bar baut.
Soll der Header beim Scrollen lieber verschwinden?
Auf Feeds mit einer schlichten Leiste oben lässt du ihn verschwinden, auf Screens mit einem echten Titel klappst du ihn ein. In einem Feed fährt die Leiste weg, während du nach unten scrollst, und kommt auf dem Weg nach oben zurück. Der Inhalt bekommt so die volle Höhe. Das ist useScrollState({ hideOnScroll: true }): hideProgress folgt dem Finger über hideDistance Pixel (standardmäßig 56) von 0 bis 1 und rastet beim Anhalten auf sichtbar oder versteckt ein.
$ npx wingo-ui@latest add use-scroll-stateWas ändern reduzierte Bewegung und Barrierefreiheit?
Bei prefers-reduced-motion wird die Übergabe beim Large Title zu einem sofortigen Wechsel, und nichts streckt sich. Der Hero verzichtet auf Parallaxe und Stretch, und seine Leiste übernimmt mit einer einzigen kurzen Überblendung. Beide Komponenten lesen die Einstellung über einen Hook, der die Hydration nicht stört. Das Problem dahinter erklärt unser Guide zu prefers-reduced-motion in React. Der große Titel bleibt das einzige h1 der Seite, der Zurück-Button und die Aktionen sind auf Touchscreens 44px groß, und wenn Escape oder „Cancel“ eine Suche über die Tastatur beendet, springt der Fokus zurück auf den Titel.
Für die übrigen Details auf dem Smartphone, von Bottom Sheets bis zu Touch-Zielen, sieh dir den Guide zu Mobile-first React Components und die Kategorie Mobile UI mit React an.
Wie geht es weiter?
Wenn du nur ein Ausblenden brauchst, reicht die selbst gebaute Version von oben. Für die gemessene Übergabe, das Einrasten, das Suchfeld und den Foto-Hero sind beide Komponenten Teil von Wingo UI Pro: Führ npx wingo-ui@latest add large-title-header hero-header aus und ersetz die Demo-Texte durch deine eigenen Screens.
Häufige Fragen
Wie verkleinere ich in React einen Header beim Scrollen ohne Re-Render?
Halte den Scroll-Offset in einem Motion Value (useScroll aus motion oder useScrollState aus Wingo UI) und leite Opacity und Transform mit useTransform davon ab. Motion Values schreiben direkt ins DOM, also rendert React nur, wenn ein Boolean wie collapsed tatsächlich umspringt.
Soll ich die Höhe des Headers beim Einklappen animieren?
Nein. Die Höhe ist eine Layout-Eigenschaft, also berechnet der Browser in jedem Frame das Layout von allem unter dem Header neu. Lass den großen Titel mit dem Inhalt wegscrollen und blende einen kompakten Titel in eine Sticky-Leiste mit fester Höhe ein, die nur Opacity und Transform animiert.
Können CSS Scroll-driven Animations das JavaScript ersetzen?
Für ein einfaches Ausblenden ja, in Chrome ab Version 115 und Safari ab Version 26. MDN führt animation-timeline im Oktober 2026 aber noch als eingeschränkt verfügbar, und CSS allein kann weder messen, wo der Titel endet, noch einen halb eingeklappten Header einrasten lassen oder auf ein Suchfeld reagieren. Behalte also einen Fallback.
Welches Element ist bei einem Large Title Header das h1?
Der große Titel. Die kompakte Kopie in der Leiste ist derselbe Text, also markier sie mit aria-hidden, sonst liest der Screenreader den Titel zweimal vor. Beim Foto-Hero behält die Seite ihr eigenes h1 unter dem Foto, und der Titel in der Leiste bleibt aria-hidden.
Sind Large Title Header und Hero Header von Wingo UI kostenlos?
Nein. Beide sind Pro-Komponenten, genau wie der Hook useScrollState, den sie gemeinsam nutzen. Mit einem Pro-Konto, angemeldet über npx wingo-ui@latest login, kopiert npx wingo-ui@latest add large-title-header die Komponente und alles, was sie importiert, in dein Projekt.
- Mobile UI
- Header
- Scroll-Animation
- iOS
- React


