Quando ocorre uma transição de visualização entre dois documentos diferentes, ela é chamada de transição de visualização de vários documentos. Isso geralmente acontece em aplicativos de várias páginas (AMP). As transições de visualização entre documentos são compatíveis com o Chrome a partir da versão 126.
Compatibilidade com navegadores
As transições de visualização entre documentos dependem dos mesmos blocos de construção e princípios das transições de visualização no mesmo documento, o que é intencional:
- O navegador faz capturas de tela de elementos que têm um
view-transition-name
exclusivo na página antiga e na nova. - O DOM é atualizado enquanto a renderização é suprimida.
- Por fim, as transições são geradas por animações CSS.
A diferença em relação às transições de visualização no mesmo documento é que, com as transições de visualização entre documentos, não é necessário chamar document.startViewTransition
para iniciar uma transição de visualização. Em vez disso, o acionador de uma transição de visualização entre documentos é uma navegação de mesma origem de uma página para outra, uma ação que geralmente é realizada pelo usuário do seu site ao clicar em um link.
Em outras palavras, não há uma API a ser chamada para iniciar uma transição de visualização entre dois documentos. No entanto, duas condições precisam ser atendidas:
- Os dois documentos precisam existir na mesma origem.
- As duas páginas precisam ativar 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ção de mesma origem. Uma navegação será 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.
Por exemplo, é possível ter uma transição de visualização entre documentos ao navegar de developer.chrome.com
para developer.chrome.com/blog
, já que eles têm a mesma origem.
Não é possível fazer essa transição ao navegar de developer.chrome.com
para www.chrome.com
, porque elas são de origem cruzada e do mesmo site.
As transições de visualização entre documentos são ativas
Para que haja uma transição de visualização entre dois documentos, as duas páginas participantes precisam permitir isso. Isso é feito com a regra @view-transition
no CSS.
Na regra @view-transition
at-rule, defina o descritor navigation
como auto
para ativar as transições de visualização para navegações entre documentos da mesma origem.
@view-transition {
navigation: auto;
}
Ao definir o descritor navigation
como auto
, você ativa as transições de visualização para os seguintes NavigationTypes:
traverse
push
oureplace
, 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 de URL ou clicar em um marcador de página, bem como qualquer forma de recarga iniciada pelo usuário ou pelo 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 das 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 pela navegação de uma página para outra.
Personalizar transições de visualização entre documentos
Para personalizar transições de visualização entre documentos, use 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 com ela.
Os eventos pageswap
e pagereveal
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 cada navegação entre documentos de mesma origem, independentemente 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, acesse o objeto ViewTransition
usando a propriedade viewTransition
nesses eventos.
- O evento
pageswap
é disparado antes que o último frame de uma página seja renderizado. Você pode usar isso para fazer algumas mudanças de última hora na página de saída, antes que os snapshots antigos sejam feitos. - O evento
pagereveal
é acionado em uma página depois de ter sido inicializado ou reativado, mas antes da primeira oportunidade de renderização. Com ele, é possível personalizar a nova página antes que os novos snapshots sejam criados.
Por exemplo, é possível usar esses eventos para definir ou mudar rapidamente alguns valores de view-transition-name
ou transmitir dados de um documento para outro, gravando e lendo dados de sessionStorage
para personalizar a transição de visualização antes de ela ser executada.
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 você quiser, 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 lidam com as várias promessas de forma diferente:
pageswap
: quando o documento é oculto, o objetoViewTransition
antigo é ignorado. Quando isso acontece,viewTransition.ready
é rejeitado eviewTransition.finished
é resolvido.pagereveal
: a promessaupdateCallBack
já foi resolvida neste ponto. É possível usar as promessasviewTransition.ready
eviewTransition.finished
.
Informações de ativação da navegação
Nos eventos pageswap
e pagereveal
, também é possível realizar ações com base nos URLs da página antiga e da nova.
Por exemplo, no navegador de pilha de MPA, 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 de visão geral, o conteúdo antigo precisa sair da esquerda para a direita.
Para fazer 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 atuais e finais do histórico de destino, conforme encontradas em navigation.entries()
na API Navigation.
Em uma página ativada, é possível acessar esse objeto usando navigation.activation
. No evento pageswap
, é possível acessar isso pelo 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.
Dessa forma, você não precisa decorar todos os itens da lista com um view-transition-name
antecipadamente. Em vez disso, isso acontece no momento certo usando JavaScript, apenas nos elementos que precisam dele.
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 faz a limpeza depois de si mesmo, removendo os valores de view-transition-name
depois que a transição da visualização é executada. Dessa forma, a página fica pronta para navegações sucessivas e também pode processar a travessia do histórico.
Para ajudar com isso, use essa função utilitária que define view-transition-name
s temporariamente.
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 agora pode ser simplificado da seguinte maneira:
// 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 o bloqueio de renderização
Compatibilidade com navegadores
Em alguns casos, convém adiar a primeira renderização de uma página até que um determinado elemento esteja presente no novo DOM. Isso evita o flash e garante que o estado em que você está animando seja estável.
No <head>
, defina um ou mais IDs de elementos que precisam estar presentes antes que a página receba a primeira renderização, 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, no caso de imagens, a simples presença da tag <img>
com o id
especificado na árvore do 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 da 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 Principais métricas da Web.
Conferir tipos de transição em transições de visualização entre documentos
As transições de visualização entre documentos também oferecem suporte a tipos de transição de visualização para personalizar as animações e quais elementos são capturados.
Por exemplo, ao acessar a página seguinte ou anterior em uma paginação, você pode usar animações diferentes, dependendo se está indo para uma página mais alta ou mais baixa da sequência.
Para definir esses tipos com antecedência, adicione os tipos na regra de at-rule @view-transition
:
@view-transition {
navigation: auto;
types: slide, forwards;
}
Para definir os tipos dinamicamente, 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
da página antiga para o objeto ViewTransition
da 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 seletor de pseudoclasse :active-view-transition-type()
da mesma forma que nas transições de visualização do 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 a qual você está navegando.
O tipo de transição a ser usado é determinado nos eventos pagereveal
e pageswap
, analisando os URLs de origem e de 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.
Caso tenha um bug, registre um bug do Chromium.