Transições de visualização entre documentos para aplicativos com várias páginas

Quando uma transição de visualização ocorre entre dois documentos diferentes, ela é chamada de transição de visualização entre documentos. Isso geralmente acontece em aplicativos de várias páginas (MPA, na sigla em inglês). As transições de visualização entre documentos são compatíveis com o Chrome a partir da versão 126.

Browser Support

  • Chrome: 126.
  • Edge: 126.
  • Firefox: not supported.
  • Safari: 18.2.

Source

As transições de visualização entre documentos usam os mesmos blocos de construção e princípios das transições de visualização no mesmo documento, o que é proposital:

  1. O navegador faz snapshots de elementos que têm um view-transition-name exclusivo nas páginas antiga e nova.
  2. O DOM é atualizado enquanto a renderização é suprimida.
  3. Por fim, as transições são alimentadas por animações CSS.

A diferença em comparação com as transições de visualização no mesmo documento é que, com as transições entre documentos, não é necessário chamar document.startViewTransition para iniciar uma transição de visualização. Em vez disso, o gatilho para uma transição de visualização entre documentos é uma navegação de mesma origem de uma página para outra, uma ação normalmente realizada pelo usuário do seu site ao clicar em um link.

Em outras palavras, não há uma API para chamar e iniciar uma transição de visualização entre dois documentos. No entanto, há duas condições que precisam ser atendidas:

  • Os dois documentos precisam estar na mesma origem.
  • As duas páginas precisam ativar a opção para permitir a transição de visualização.

Ambas as condições são explicadas mais adiante neste documento.


As transições de visualização entre documentos são limitadas a navegações de mesma origem.

As transições de visualização entre documentos são limitadas apenas a navegações de mesma origem. Uma navegação é considerada de mesma origem se a origem das duas páginas participantes for a mesma.

A origem de uma página é uma combinação do esquema, do nome do host e da porta usados, conforme detalhado em web.dev.

Um exemplo de URL com o esquema, o nome do host e a porta destacados. Combinados, eles formam a origem.
Um exemplo de URL com o esquema, o nome do host e a porta destacados. Combinados, eles formam a origem.

Por exemplo, você pode ter uma transição de visualização entre documentos ao navegar de developer.chrome.com para developer.chrome.com/blog, já que eles são de mesma origem. Não é possível ter essa transição ao navegar de developer.chrome.com para www.chrome.com, já que são entre origens e do mesmo site.


As transições de visualização entre documentos são opcionais

Para ter uma transição de visualização entre dois documentos, as páginas participantes precisam permitir isso. Isso é feito com a regra @@view-transition em CSS.

Na regra @@view-transition, defina o descritor navigation como auto para ativar as transições de visualização em navegações entre documentos da mesma origem.

@view-transition {
  navigation: auto;
}

Ao definir o descritor navigation como auto, você permite que as transições de visualização ocorram para os seguintes NavigationTypes:

  • traverse
  • push ou replace, se a ativação não foi iniciada pelo usuário usando mecanismos da interface do navegador.

As navegações excluídas de auto são, por exemplo, navegar usando a barra de endereço do URL ou clicar em um marcador, bem como qualquer forma de recarregamento iniciado pelo usuário ou script.

Se uma navegação demorar muito (mais de quatro segundos no caso do Chrome), a transição de visualização será ignorada com um TimeoutError DOMException.

Demonstração de transições de visualização entre documentos

Confira a demonstração a seguir, que usa transições de visualização para criar uma demonstração do Stack Navigator. Não há chamadas para document.startViewTransition() aqui. As transições de visualização são acionadas ao navegar de uma página para outra.

Gravação da demonstração do Stack Navigator. É necessário o Chrome 126 ou mais recente.

Personalizar transições de visualização entre documentos

Para personalizar as transições de visualização entre documentos, você pode usar alguns recursos da plataforma da Web.

Esses recursos não fazem parte da especificação da API View Transition, mas foram projetados para serem usados em conjunto com ela.

Os eventos pageswap e pagereveal

Browser Support

  • Chrome: 124.
  • Edge: 124.
  • Firefox: not supported.
  • Safari: 18.2.

Source

Para permitir que você personalize as transições de visualização entre documentos, a especificação HTML inclui dois novos eventos que podem ser usados: pageswap e pagereveal.

Esses dois eventos são acionados para toda navegação entre documentos de mesma origem, independente de uma transição de visualização estar prestes a acontecer ou não. Se uma transição de visualização estiver prestes a acontecer entre as duas páginas, você poderá acessar o objeto ViewTransition usando a propriedade viewTransition nesses eventos.

  • O evento pageswap é acionado antes da renderização do último frame de uma página. Você pode usar isso para fazer algumas mudanças de última hora na página de saída, logo antes de os snapshots antigos serem tirados.
  • O evento pagereveal é acionado em uma página depois que ela é inicializada ou reativada, mas antes da primeira oportunidade de renderização. Com ele, você pode personalizar a nova página antes de tirar os novos snapshots.

Por exemplo, é possível usar esses eventos para definir ou mudar rapidamente alguns valores de view-transition-name ou passar dados de um documento para outro gravando e lendo dados de sessionStorage para personalizar a transição de visualização antes da execução.

let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
  if (event.target.tagName.toLowerCase() === 'a') return;
  lastClickX = event.clientX;
  lastClickY = event.clientY;
});

// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
  if (event.viewTransition && lastClick) {
    sessionStorage.setItem('lastClickX', lastClickX);
    sessionStorage.setItem('lastClickY', lastClickY);
  }
});

// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
  if (event.viewTransition) {
    lastClickX = sessionStorage.getItem('lastClickX');
    lastClickY = sessionStorage.getItem('lastClickY');
  }
});

Se quiser, você pode pular a transição nos dois eventos.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    if (goodReasonToSkipTheViewTransition()) {
      e.viewTransition.skipTransition();
    }
  }
}

O objeto ViewTransition em pageswap e pagereveal são dois objetos diferentes. Elas também processam as várias promessas de maneira diferente:

  • pageswap: depois que o documento é ocultado, o objeto ViewTransition antigo é ignorado. Quando isso acontece, viewTransition.ready rejeita e viewTransition.finished resolve.
  • pagereveal: a promessa updateCallBack já foi resolvida neste ponto. Você pode usar as promessas viewTransition.ready e viewTransition.finished.

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: 147.
  • Safari: 26.2.

Source

Nos eventos pageswap e pagereveal, também é possível realizar ações com base nos URLs das páginas antigas e novas.

Por exemplo, no MPA Stack Navigator, o tipo de animação a ser usado depende do caminho de navegação:

  • Ao navegar da página de visão geral para uma página de detalhes, o novo conteúdo precisa deslizar da direita para a esquerda.
  • Ao navegar da página de detalhes para a página de visão geral, o conteúdo antigo precisa deslizar da esquerda para a direita.

Para isso, você precisa de informações sobre a navegação que, no caso de pageswap, está prestes a acontecer ou, no caso de pagereveal, acabou de acontecer.

Para isso, os navegadores agora podem expor objetos NavigationActivation que contêm informações sobre a navegação de mesma origem. Esse objeto expõe o tipo de navegação usado, as entradas de histórico de destino atuais e finais, conforme encontrado em navigation.entries() da API Navigation.

Em uma página ativada, é possível acessar esse objeto usando navigation.activation. No evento pageswap, é possível acessar isso por e.activation.

Confira esta demonstração de perfis que usa informações de NavigationActivation nos eventos pageswap e pagereveal para definir os valores de view-transition-name nos elementos que precisam participar da transição de visualização.

Assim, não é necessário decorar todos os itens da lista com um view-transition-name antecipadamente. Em vez disso, isso acontece just-in-time usando JavaScript, apenas em elementos que precisam dele.

Gravação da demonstração de perfis. É necessário ter o Chrome 126 ou uma versão mais recente.

O código é este:

// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
  if (e.viewTransition) {
    const targetUrl = new URL(e.activation.entry.url);

    // Navigating to a profile page
    if (isProfilePage(targetUrl)) {
      const profile = extractProfileNameFromUrl(targetUrl);

      // Set view-transition-name values on the clicked row
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';

      // Remove view-transition-names after snapshots have been taken
      // (this to deal with BFCache)
      await e.viewTransition.finished;
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
    }
  }
});

// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
  if (e.viewTransition) {
    const fromURL = new URL(navigation.activation.from.url);
    const currentURL = new URL(navigation.activation.entry.url);

    // Navigating from a profile page back to the homepage
    if (isProfilePage(fromURL) && isHomePage(currentURL)) {
      const profile = extractProfileNameFromUrl(currentURL);

      // Set view-transition-name values on the elements in the list
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';

      // Remove names after snapshots have been taken
      // so that we're ready for the next navigation
      await e.viewTransition.ready;
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
    }
  }
});

O código também se limpa removendo os valores view-transition-name depois que a transição de visualização é executada. Assim, a página fica pronta para navegações sucessivas e também pode lidar com a travessia do histórico.

Para ajudar com isso, use esta função utilitária que define temporariamente view-transition-names.

const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
  for (const [$el, name] of entries) {
    $el.style.viewTransitionName = name;
  }

  await vtPromise;

  for (const [$el, name] of entries) {
    $el.style.viewTransitionName = '';
  }
}

O código anterior pode ser simplificado da seguinte forma:

// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
  if (e.viewTransition) {
    const targetUrl = new URL(e.activation.entry.url);

    // Navigating to a profile page
    if (isProfilePage(targetUrl)) {
      const profile = extractProfileNameFromUrl(targetUrl);

      // Set view-transition-name values on the clicked row
      // Clean up after the page got replaced
      setTemporaryViewTransitionNames([
        [document.querySelector(`#${profile} span`), 'name'],
        [document.querySelector(`#${profile} img`), 'avatar'],
      ], e.viewTransition.finished);
    }
  }
});

// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
  if (e.viewTransition) {
    const fromURL = new URL(navigation.activation.from.url);
    const currentURL = new URL(navigation.activation.entry.url);

    // Navigating from a profile page back to the homepage
    if (isProfilePage(fromURL) && isHomePage(currentURL)) {
      const profile = extractProfileNameFromUrl(currentURL);

      // Set view-transition-name values on the elements in the list
      // Clean up after the snapshots have been taken
      setTemporaryViewTransitionNames([
        [document.querySelector(`#${profile} span`), 'name'],
        [document.querySelector(`#${profile} img`), 'avatar'],
      ], e.viewTransition.ready);
    }
  }
});

Aguardar o carregamento do conteúdo com bloqueio de renderização

Browser Support

  • Chrome: 124.
  • Edge: 124.
  • Firefox: not supported.
  • Safari: not supported.

Em alguns casos, talvez você queira adiar a primeira renderização de uma página até que um determinado elemento esteja presente no novo DOM. Isso evita o efeito de flash e garante que o estado para o qual você está animando esteja estável.

No <head>, defina um ou mais IDs de elementos que precisam estar presentes antes da primeira renderização da página usando a seguinte metatag.

<link rel="expect" blocking="render" href="#section1">

Essa metatag significa que o elemento precisa estar presente no DOM, não que o conteúdo precisa ser carregado. Por exemplo, com imagens, a simples presença da tag <img> com o id especificado na árvore DOM é suficiente para que a condição seja avaliada como verdadeira. A imagem ainda pode estar sendo carregada.

Antes de bloquear a renderização, saiba que a renderização incremental é um aspecto fundamental da Web. Portanto, tenha cuidado ao optar por bloquear a renderização. O impacto do bloqueio de renderização precisa ser avaliado caso a caso. Por padrão, evite usar blocking=render, a menos que você possa medir e avaliar ativamente o impacto que ele tem nos usuários, medindo o impacto nas Core Web Vitals.


Ver tipos de transição em transições de visualização entre documentos

As transições de visualização entre documentos também são compatíveis com tipos de transição de visualização para personalizar as animações e quais elementos são capturados.

Por exemplo, ao ir para a próxima ou a página anterior em uma paginação, você pode usar animações diferentes dependendo se está indo para uma página mais alta ou mais baixa na sequência.

Para definir esses tipos antecipadamente, adicione-os à regra at @view-transition:

@view-transition {
  navigation: auto;
  types: slide, forwards;
}

Para definir os tipos de maneira dinâmica, use os eventos pageswap e pagereveal para manipular o valor de e.viewTransition.types.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
    e.viewTransition.types.add(transitionType);
  }
});

Os tipos não são transferidos automaticamente do objeto ViewTransition na página antiga para o objeto ViewTransition na nova página. Você precisa determinar os tipos a serem usados em pelo menos a nova página para que as animações sejam executadas conforme o esperado.

Para responder a esses tipos, use o pseudoseletor de pseudoclasse :active-view-transition-type() da mesma forma que nas transições de visualização no mesmo documento.

/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
  :root {
    view-transition-name: none;
  }
  article {
    view-transition-name: content;
  }
  .pagination {
    view-transition-name: pagination;
  }
}

/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-to-left;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-from-right;
  }
}

/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-to-right;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-from-left;
  }
}

/* Animation styles for reload type only */
html:active-view-transition-type(reload) {
  &::view-transition-old(root) {
    animation-name: fade-out, scale-down;
  }
  &::view-transition-new(root) {
    animation-delay: 0.25s;
    animation-name: fade-in, scale-up;
  }
}

Como os tipos só se aplicam a uma transição de visualização ativa, eles são limpos automaticamente quando uma transição de visualização é concluída. Por isso, os tipos funcionam bem com recursos como o BFCache.

Demonstração

Na demonstração de paginação a seguir, o conteúdo da página desliza para frente ou para trás com base no número da página para que você está navegando.

Gravação da demonstração de paginação (MPA). Ele usa transições diferentes dependendo da página para onde você está indo.

O tipo de transição a ser usado é determinado nos eventos pagereveal e pageswap ao analisar os URLs de origem e destino.

const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
  const currentURL = new URL(fromNavigationEntry.url);
  const destinationURL = new URL(toNavigationEntry.url);

  const currentPathname = currentURL.pathname;
  const destinationPathname = destinationURL.pathname;

  if (currentPathname === destinationPathname) {
    return "reload";
  } else {
    const currentPageIndex = extractPageIndexFromPath(currentPathname);
    const destinationPageIndex = extractPageIndexFromPath(destinationPathname);

    if (currentPageIndex > destinationPageIndex) {
      return 'backwards';
    }
    if (currentPageIndex < destinationPageIndex) {
      return 'forwards';
    }

    return 'unknown';
  }
};

Feedback

O feedback dos desenvolvedores é sempre bem-vindo. Para compartilhar, registre um problema com o Grupo de Trabalho do CSS no GitHub com sugestões e perguntas. Adicione o prefixo [css-view-transitions] ao seu problema. Se você encontrar um bug, registre um bug do Chromium.