View Transitions API: animações de página nativas sem biblioteca

1. Introdução à View Transitions API

A View Transitions API é uma especificação nativa do navegador que permite criar animações suaves entre estados de uma página — seja durante navegações entre rotas em Single Page Applications (SPA) ou entre páginas completas em Multi-Page Applications (MPA). Diferente de bibliotecas como GSAP, Framer Motion ou soluções baseadas em React Transition Group, essa API opera diretamente no motor de renderização do navegador, eliminando a necessidade de carregar kilobytes de JavaScript apenas para efeitos visuais.

O principal problema resolvido é a complexidade de implementar transições fluidas entre páginas sem perder performance. Tradicionalmente, desenvolvedores precisavam gerenciar manualmente o ciclo de vida de animações, estados de carregamento e sincronização com dados assíncronos. A View Transitions API abstrai todo esse processo, capturando automaticamente o estado visual antes e depois da mudança.

2. Como funciona o mecanismo de transição

O coração da API é o conceito de "snapshot" — uma captura instantânea da tela atual. Quando uma transição é iniciada, o navegador:

  1. Tira um screenshot da página atual (estado "old")
  2. Aplica as mudanças no DOM (nova rota, novos dados)
  3. Tira um screenshot do novo estado (estado "new")
  4. Interpola entre os dois usando animações CSS padrão

Esses snapshots são representados por pseudo-elementos especiais na árvore de transição:

::view-transition
  └── ::view-transition-group(root)
       ├── ::view-transition-image-pair(root)
       │    ├── ::view-transition-old(root)
       │    └── ::view-transition-new(root)

O ciclo de vida é controlado pelo método document.startViewTransition(callback), que retorna uma promessa. Se a API não for suportada, o callback é executado normalmente sem animação — garantindo fallback automático.

3. Configuração básica: transições entre páginas SPA

Em aplicações SPA, a implementação mínima envolve envolver a lógica de navegação com startViewTransition. Exemplo com React Router:

import { useNavigate } from 'react-router-dom';

function navigateWithTransition(to) {
  if (document.startViewTransition) {
    document.startViewTransition(() => {
      // React Router atualizará o DOM dentro deste callback
      navigate(to);
    });
  } else {
    navigate(to);
  }
}

Para frameworks reativos como Svelte ou Astro, o princípio é o mesmo — qualquer mudança que altere o DOM pode ser encapsulada:

// Exemplo com Svelte
async function handleNavigation(event) {
  event.preventDefault();
  const url = event.target.href;

  if (document.startViewTransition) {
    await document.startViewTransition(async () => {
      await navigate(url); // função que atualiza o estado da rota
    }).finished;
  } else {
    await navigate(url);
  }
}

4. Transições entre páginas MPA (Multi-Page Application)

Para sites tradicionais com navegação completa entre páginas HTML, a ativação é ainda mais simples — basta adicionar uma meta tag no <head>:

<meta name="view-transition" content="same-origin">

Isso habilita transições automáticas entre páginas do mesmo domínio. O navegador gerencia todo o processo: captura a página atual, carrega a nova URL e anima a transição. A limitação principal é a falta de controle granular — você não pode personalizar animações por elemento ou sincronizar com dados carregados via AJAX.

5. Personalização de animações com CSS

As animações padrão (fade crossfade) podem ser substituídas por @keyframes customizados. Os seletores específicos permitem controle preciso:

/* Animação de slide horizontal */
@keyframes slide-from-right {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}

@keyframes slide-to-left {
  from { transform: translateX(0); }
  to { transform: translateX(-100%); }
}

::view-transition-old(root) {
  animation: 300ms ease-in slide-to-left;
}

::view-transition-new(root) {
  animation: 300ms ease-out slide-from-right;
}

Para fade-in/fade-out personalizado:

@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

::view-transition-old(root) {
  animation: 200ms ease-out fade-out;
}

::view-transition-new(root) {
  animation: 200ms ease-in fade-in;
}

6. Transições de elementos compartilhados (cross-fade)

Um dos recursos mais poderosos é a capacidade de animar elementos específicos que mudam de posição, tamanho ou forma entre estados. Basta atribuir um view-transition-name único no CSS:

/* Card que aparece em duas páginas diferentes */
.card-destaque {
  view-transition-name: card-principal;
  /* O navegador automaticamente animará a transição entre posições */
}

Caso de uso prático — galeria de imagens:

/* Cada imagem da galeria recebe um nome único */
.galeria img:nth-child(1) { view-transition-name: img-1; }
.galeria img:nth-child(2) { view-transition-name: img-2; }
/* Na página de detalhes, o mesmo nome é usado para a imagem ampliada */

O navegador calcula automaticamente a interpolação de posição, escala e recorte, criando uma sensação de continuidade visual.

7. Controle avançado com JavaScript

O objeto ViewTransition retornado por startViewTransition oferece hooks para controle fino:

async function transicaoControlada(url) {
  const transition = document.startViewTransition(async () => {
    // Carrega dados primeiro
    const dados = await fetchData(url);
    atualizarDOM(dados);
  });

  // Aguarda a animação terminar
  await transition.finished;

  // Ou cancela se necessário
  // transition.skipTransition();
}

Para sincronizar com carregamento de dados pesados:

document.startViewTransition(async () => {
  // Mostra skeleton loading enquanto carrega
  mostrarSkeleton();
  const dados = await fetch('/api/dados');
  atualizarConteudo(dados);
  esconderSkeleton();
});

8. Boas práticas, acessibilidade e suporte

A acessibilidade é fundamental — respeite a preferência do usuário por movimento reduzido:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}

Para performance, monitore o Cumulative Layout Shift (CLS) — transições bruscas podem causar saltos visuais. A API ajuda a mitigar isso, mas certifique-se de que os elementos tenham dimensões definidas antes da transição.

Suporte atual (2025): Chrome/Edge 111+, Firefox em desenvolvimento (flag experimental), Safari ainda sem suporte nativo. Para fallback, a API já degrada graciosamente — sem polyfill necessário em muitos casos.

Referências