View Transitions in Next.js: paginaanimaties zonder animatiebibliotheek
View Transitions in Next.js: paginaanimaties zonder animatiebibliotheek
In het laatste artikel liet ik zien dat INP het verschil meet tussen een klik en het frame dat die weergeeft, en dat één lange taak op de hoofdthread dat verschil kan ruïneren. Paginatransities zijn de extreme versie van dat probleem: ze vragen de hoofdthread zijn duurste werk te doen — een hele nieuwe pagina renderen — en tegelijkertijd een vloeiende 60fps-animatie te draaien.
Wat de bibliotheken echt deden: FLIP
In 2015 beschreef Paul Lewis een techniek die hij FLIP noemde – First, Last, Invert, Play. De View Transitions API neemt die truc over en geeft hem aan de browser. In plaats van twee levende DOM-bomen bij te houden, fotografeert de browser de oude staat, laat het DOM bijwerken, fotografeert de nieuwe staat en animeert tussen de twee afbeeldingen – op de compositor.
javascript
function navigate(update) {if (!document.startViewTransition) {update(); // geen ondersteuning: gewoon bijwerken, geen animatiereturn;}document.startViewTransition(() => update());}
css
::view-transition /* de overlay die de hele pagina bedekt */::view-transition-group(name) /* animeert grootte en positie */::view-transition-image-pair(name)::view-transition-old(name) /* de "voor"-snapshot */::view-transition-new(name) /* de "na"-snapshot */
Wat iedereen verrast: een snapshot is een foto, geen element
::view-transition-old is niet uw oude element kleiner gerenderd. Het is een statische afbeelding ervan. Een <video> in een transitie bevriest op één frame. Een spinner stopt. Tekst in de snapshot kan niet worden geselecteerd.
Bij 200ms maakt niets dat uit. Bij 1000ms wordt alles zichtbaar en licht kapot.
Naamgeving: het hele contract
Dezelfde naam aan beide kanten betekent dat de browser het ene in het andere beweegt en schaalt. De harde regel: een view-transition-name moet uniek in het document zijn.
css
.card-image {view-transition-name: hero;}::view-transition-group(hero) {animation-duration: 400ms;animation-timing-function: ease-in-out;}
React 19.2 en Next.js 16: één component
tsx
import { ViewTransition } from "react";import Image from "next/image";import Link from "next/link";export function ProjectCard({ project }) {return (<Link href={"/projects/" + project.slug}><ViewTransition name={"project-" + project.slug}><Image src={project.cover} alt={project.title} /></ViewTransition></Link>);}
share en default="none" moeten samen reizen. Met default="none" en geen share-prop stopt het genoemde paar stil met morphen.
Richting: de gebruiker vertellen waar hij naartoe is gegaan
tsx
<Link href="/projects/runvalis" transitionTypes={["nav-forward"]}>Runvalis</Link><ViewTransitionenter={{ "nav-forward": "slide-in", "nav-back": "slide-out", default: "none" }}exit={{ "nav-forward": "slide-out", "nav-back": "slide-in", default: "none" }}default="none"><main>{children}</main></ViewTransition>
De versie zonder framework
css
@view-transition {navigation: auto;}
Valkuilen en best practices
- Altijd behandelen als progressive enhancement.
- Debuggen in slow motion. Het Animations-paneel in Chrome DevTools laat afspelen reduceren naar 25% of 10%.
prefers-reduced-motionrespecteren.- De overlay eet standaard klikken.
::view-transition { pointer-events: none }instellen. - Snapshots kosten geheugen.
- De header verankeren.
- Een view transition is onzichtbaar voor ondersteunende technologie.
Conclusie
View Transitions zijn hetzelfde argument toegepast op navigatie: in plaats van FLIP met de hand uit te voeren, declareer je wat twee staten verbindt en laat je de browser de verbinding uit twee foto's tekenen.