Capítulo 97 de 456
O componente <ViewTransition> do React se integra à View Transitions API do browser para animar declarativamente transições entre estados de UI (navegação, loading, troca de conteúdo), sem bibliotecas de animação complexas — funciona out-of-the-box no App Router (React canary).
<ViewTransition> (de react): componente que ativa animação via name/key; disponível sem instalar react@canary manualmente, pois o App Router já usa canary releases.useTransition), <Suspense> e useDeferredValue ativam <ViewTransition>; setState comum não ativa. Navegações de rota no Next.js já são transitions, então ativam automaticamente.<ViewTransition> com o mesmo name em páginas diferentes fazem o browser animar automaticamente posição/tamanho entre eles (ex. thumbnail → hero image). Só funciona se o conteúdo de destino renderiza no mesmo commit da navegação (páginas prefetched); se suspende num fallback primeiro, não forma par.share/default="none": share="morph" atribui a classe morph à view transition para customizar via CSS pseudo-elementos; default="none" impede que um <ViewTransition> nomeado anime em toda transição não relacionada da página — sem ele, todo par nomeado anima sempre que qualquer transição roda.enter/exit: props para animar entrada/saída (ex. Suspense fallback saindo com exit="slide-down", conteúdo real entrando com enter="slide-up").transitionTypes (no <Link>/useRouter().push()/.replace()): marca a navegação com um tipo (ex. nav-forward, nav-back) não automático — você decide qual link é "forward" e qual é "back". enter/exit podem ser objetos chaveados por tipo de transição para mapear direção.viewTransitionName em elemento fixo (ex. header): usado com CSS (animation: none, display: none no old snapshot) para ancorar um elemento que não deve se mover durante slides direcionais.::view-transition { pointer-events: none; }: necessário porque o overlay de transição captura cliques por padrão, perdendo interações durante a animação.<ViewTransition key={slug} share="auto" enter="auto"> — mudar o key faz React tratar conteúdo antigo/novo como par exit/enter (ativando share) em vez de update in-place, ideal para trocar conteúdo dentro da mesma rota (ex. tabs).page.tsx, não no layout: layouts persistem entre navegações, então enter/exit nunca disparam ali.view-transition-class), disponíveis em Chromium 125+ e versões recentes de Safari/Firefox; sem suporte, o app funciona normalmente mas sem animação.import { ViewTransition } from 'react'
function PhotoGrid({ photos }) {
return photos.map((photo) => (
<Link key={photo.id} href={`/photo/${photo.id}`}>
<ViewTransition name={`photo-${photo.id}`}>
<Image src={photo.src} alt={photo.title} />
</ViewTransition>
</Link>
))
}
name na grid e na página de detalhe faz o browser animar a transição de posição/tamanho automaticamente.<Suspense fallback={<ViewTransition exit="slide-down" default="none"><PhotoContentSkeleton /></ViewTransition>}>
<ViewTransition enter="slide-up" default="none">
<PhotoContent id={id} />
</ViewTransition>
</Suspense>
slide-down, conteúdo real entra com slide-up.<ViewTransition
enter={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
exit={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
default="none"
>
{/* page content */}
</ViewTransition>
transitionTypes do <Link>) para animações direcionais de entrada/saída.<ViewTransition key={slug} name="collection-content" share="auto" enter="auto" default="none">
<CollectionGrid slug={slug} />
</ViewTransition>
key pelo slug faz o conteúdo antigo/novo virar par exit/enter em vez de update silencioso.| Padrão | O que comunica |
|---|---|
| Shared element (morph) | "Mesma coisa, indo mais fundo" |
| Suspense reveal | "Dado carregado" |
| Directional slide | "Indo pra frente / voltando" |
| Crossfade same-route | "Mesmo lugar, conteúdo diferente" |
default="none" sem share explícito num par nomeado: o par silenciosamente para de morphar.page.tsx: layouts persistem entre navegações, então enter/exit nunca disparam.prefers-reduced-motion em slides direcionais: movimento de posição no viewport é o gatilho mais comum de sensibilidade a movimento — zere animation-duration/animation-delay nesse media query.pointer-events: none no overlay geral), então mantenha transições curtas.transitionTypes, então o slide direcional não toca (mas o morph de shared element ainda funciona se os name combinarem).useDeferredValue — não via setState comum; navegação de rota no Next.js já conta como transition.name igual em duas telas = morph automático; key diferente na mesma tela = crossfade (via share="auto").default="none" é quase sempre necessário em pares nomeados customizados, para não animar em transições não relacionadas.transitionTypes implementa manualmente — não é automático.pointer-events do overlay e prefers-reduced-motion como parte do checklist de implementação, não como polish opcional.transitionTypes é uma prop de <Link>/useRouter, documentada lá.