Forem

Cover image for Criando transições com API ViewTransition
Valdeir S.
Valdeir S.

Posted on • Edited on • Originally published at valdeir.dev

3

Criando transições com API ViewTransition

Nesta postagem falarei sobre uma API muito bacana e que pouca gente está comentando: a View Transitions.

No texto há diversos links, clique neles para ter acesso à implementação no browser.


O que é

A API ViewTransition foi desenvolvida para tornar mais fácil e eficiente a criação de animações e de efeitos visuais.. Um dos benefícios de usá-la é que ela permite transições mais suaves entre páginas e estados de elementos.

No Google Chrome 76, foi implementado o sistema Paint Holding: sistema para corrigir flashes brancos que acontecia quando o usuário mudava de página e o todo o conteúdo era renderizado (desenhado) na tela. No entanto, algumas mudanças ainda ocorriam de forma abrupta e repentina.

Embora a utilização dessa API traga benefícios em termos de animação e transições, é fundamental considerar possíveis problemas de acessibilidade e usabilidade. A composição de dois estados distintos pode causar problemas com elementos aria-wai em leitores de tela.


Criando transições com a nova API

Para iniciar uma transição, basta executar as modificações do DOM dentro da função document.startViewTransition. No momento que o DOM do HTML for alterado, o navegador criará, no topo da estrutura HTML, uma árvore de pseudoelementos, que será removida após a conclusão da transição.

document.startViewTransition(() => {
    document.body.innerHTML = `<your code>`;
});
Enter fullscreen mode Exit fullscreen mode

Árvore de pseudoelementos

Uma vez que o documento captura os estados novo e velho da página ou do elemento a ser modificado, uma estrutura de pseudoelementos como esta é criada:

html
|_::view-transition
  ├─ ::view-transition-group(name)
  │  └─ ::view-transition-image-pair(name)
  │     ├─ ::view-transition-old(name)
  │     └─ ::view-transition-new(name)
  └─ …Other groups…

Enter fullscreen mode Exit fullscreen mode

A função de cada uma deles:

Pseudoelemento Descrição
::view-transition Empilha os grupos de cada elemento que sofrerá transição
::view-transition-group Agrupa os estados dos elementos que sofrerão a transição
::view-transition-image-pair Armazena uma imagem de cada estado: velho e novo
::view-transition-old É o elemento que armazena o estado (uma espécie de screenshot) velho do elemento de transição
::view-transition-new É o elemento que armazena o estado (uma espécie de screenshot) novo, que será atualizado

Por padrão, a transição terá o efeito de fade: quando um elemento se esvaece e outro aparece no lugar.


Como as transições funcionam

As etapas de uma transição realizada sem erro ou interrupções são as seguintes:

  1. A função document.startViewTransition(callback) é invocada e retorna a interface ViewTransition;
  2. O estado atual da página é capturado;
  3. A renderização é pausada;
  4. O callback do passo 1 é executado. Ele é o responsável por realizar as alterações;
  5. A promise ViewTransition.updateCallbackbone é cumprida. Ela pode ser utilizada para você saber quando a alteração no DOM será atualiada;
  6. O novo estado da página, após a mudanças no passo 4, é capturado;
  7. Os pseudoelementos são criados na raiz da página (dentro do <html>);
  8. O estado do passo 2 é aplicado no pseudoelemento;
  9. A promise ViewTransition.ready é cumprida. Ela poderá ser usada para identificar quando a transição estará pronta para iniciar (mais abaixo terá um exemplo com ela)
  10. Ocorre a transição entre os pseudoelementos;
  11. A promise ViewTransition.finished é cumprida.

Criando uma transição simples

Vamos começar com uns exemplos bem simples para facilitar o entendimento, né? 👍

function changePage(data) {
    // Altera o conteúdo sem o uso das transições caso o navegador
    // não suporte a operação
    if (!document.startViewTransition) {
        changeContentPage(data);
        return;
    }

    document.startViewTransition(() => changeContentPage(data));
}
Enter fullscreen mode Exit fullscreen mode

Com isso, teremos o seguinte resultado:

Olha só, é super fácil! E dá para melhorar ainda mais. Bora colocar uma animação usando CSS.

::view-transition-old(root) {
    animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
        300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}

::view-transition-new(root) {
    animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
        300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

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

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

@keyframes slide-from-right {
    from { transform: translateX(30px); }
}

@keyframes slide-to-left {
    to { transform: translateX(-30px); }
}
Enter fullscreen mode Exit fullscreen mode

Eis o novo resultado:

Não é tudo. Você tem liberdade para escolher quais elementos sofrerão a transição. 👇


Criando transição com múltiplos elementos

A criação entre dois elementos segue o mesmo padrão que os exemplos anteriores. No entanto, para fazer esta transição, você precisa nomear os elementos com CSS, usando a propriedade: view-transition-name. Assim, o navegador criará um novo grupo de pseudoelementos que mover-se-ão de uma posição para outra. Quer saber mais? Olha esse exemplo que legal.

Verifique que nomeamos os três elementos que sofreram a transição

#header-menu {
  view-transition-name: header-menu;
}
#first {
  view-transition-name: el-first;
}
#second {
  view-transition-name: el-second;
}
Enter fullscreen mode Exit fullscreen mode

E também alteramos o modo de transição dos elementos #first e #second:

::view-transition-old(el-first) {
  animation: scale-old 3s both ease-in-out;
}
::view-transition-new(el-first) {
  animation: scale-new 3s both ease-in-out;
}

/* --------------------------------------------------- */

::view-transition-old(el-second) {
  animation: back-in-up 3s;
}
::view-transition-new(el-second) {
  animation: back-in-down 3s;
}
Enter fullscreen mode Exit fullscreen mode

Bem legal, né? Às vezes, precisamos lidar com animações mais complexas, então é legal se preocupar com o debug.


Debugando transições

Nem todos gostam de fazer testes, mas é necessário😁

Nesta seção, aprenderemos como debugar as animações CSS em seu site.

Para isso, basta abrir o DevTools (F12) e escolher a aba "Animations" para ter acesso a animações do seu site. Clicando no botão "pause", você consegue parar a transição, verificar as propriedades, tentar umas novas modificações.

Bem simples. Você também poderá verificar performance e emular outros dispositivos, redução de animação etc.


Criando uma transição entre páginas (SPA - Single Page Application)

Você pode mudar de página ou documento com a mesma facilidade. Mas atenção: esse método é exclusivo para SPA (pelo menos, por enquanto).

Segue um exemplo maroto (funcionará apenas com os nomes Renata e Pâmela).

Esse ficou legal, mas vamos aos comentários de alguns trechos do código e descobrir mais?

Neste evento, toda a navegação do site será interceptada para capturamos a URL atual e a de destino. Será importante para nomearmos os elementos da página inicial.

navigation.addEventListener('navigate', (event) => {
    const toUrl = new URL(event.destination.url);
    if (location.origin !== toUrl.origin) return;

    const fromUrl = new URL(location.pathname, location.href);

    event.intercept({
        async handler() {
            onLinkNavigate(toUrl, fromUrl);
        }
    })
});
Enter fullscreen mode Exit fullscreen mode

Esse trecho é opcional. Você pode trocá-lo por uma função qualquer e utilizar o evento onclick nos elementos do tipo Anchor para invocá-la.

Nesta etapa, o conteúdo da página de destino será capturado com a função getPage (que utiliza a API fetch para realizar a requisição)

async function onLinkNavigate(toUrl, fromUrl) {
    const response = await getPage(toUrl.href);

    ...
}
Enter fullscreen mode Exit fullscreen mode

Ainda no onLinkNavigate, temos o código abaixo para capturar o *card* da dançarina escolhida e nomear os elementos html corretamente. A nomeação durante a execução é necessária, pois não é possível ter dois ou mais elementos com o mesmo view-transition-name.

/**
 * @param {{ personImage: HTMLImageElement, personName: HTMLDivElement }} elementsTarget
 */
function defineViewTransitionName(elementsTarget) {
    elementsTarget.personImage.classList.add('person-image');
    elementsTarget.personName.classList.add('person-name');
}

async function onLinkNavigate(toUrl, fromUrl) {
    ...

    if (toUrl.pathname.startsWith('/dancers')) {
        elementsTarget = getTargetElements(toUrl);
        if (elementsTarget) defineViewTransitionName(elementsTarget);
    }

    ...
}
Enter fullscreen mode Exit fullscreen mode

Após os elementos serem identificados e nomeados, iniciamos a substituição de todo o conteúdo body antigo pela nova página.

async function onLinkNavigate(toUrl, fromUrl) {

    ...

    const transition = document.startViewTransition(() => {
        document.body.innerHTML = response;

            /**
             * Se a requisição for de /dancers/* para /index.html
             * então nomearemos o card da dançarina
             */
        if (fromUrl.pathname.startsWith('/dancers')) {
            elementsTarget = getTargetElements(fromUrl);
            if (elementsTarget) defineViewTransitionName(elementsTarget);
        }
    });

    transition.finished.then(() => {
        if (elementsTarget) {
            elementsTarget.personImage.classList.remove('person-image');
            elementsTarget.personName.classList.remove('person-name');
        }
    });

    ...

}
Enter fullscreen mode Exit fullscreen mode

Após a conclusão da transição, removeremos as classes que definem a propriedade view-transition-name para evitar duplicidade.

Simples, né? 💁

Mas podemos fazer muito mais.


Criando transições com o javascript 😯

Já acabou, Jéssica?? Nops!!!

Neste último exemplo, veremos como fazer uma animação usando Javascript.

Como vimos anteriormente, a promise transition.ready será cumprida quando a transição estiver pronta para começar. Nela, adicionamos a animação personalizada. É importante executar a transição nesta promise, pois é o momento que o browser capturou os dois estados necessários (o novo e o velho) e fez a composição.

No exemplo acima, usamos a API nativa Element.animate, porque ela dá o suporte necessário para pseudoelementos.

transition.ready.then(() => {
    document.documentElement.animate(
      [
        { transform: "rotate(0) scale(1)" },
        { transform: "rotate(360deg) scale(0)" },
      ],
      {
        duration: 1500,
        easing: "cubic-bezier(0.68,-0.55,0.27,1.55)",
        pseudoElement: "::view-transition-old(image)",
      }
    );

    document.documentElement.animate(
      {
        clipPath: [`circle(0 at 100px 100px)`, `circle(200px at 100px 100px)`],
      },
      {
        duration: 1500,
        easing: "ease-in",
        pseudoElement: "::view-transition-new(image)",
      }
    );
  });
Enter fullscreen mode Exit fullscreen mode

Trabalhando com frameworks 💁

Caso você trabalhe com algum framework front-end, é possível também utilizar o startViewTransition. Abaixo segue alguns exemplos com alguns deles.

Vue JS
A chave aqui é `nextTick`, que cumpre a promise uma vez que o DOM foi atualizado. Conferir um exemplo.
Svelte
Muito semelhante ao Vue, mas o método para aguardar a próxima mudança é `tick`. Conferir um exemplo.
Lit
A chave aqui é a promessa `this.updateComplete`, que cumpre a promise uma vez que o DOM foi atualizado. Conferir um exemplo.
Angular
Utilize `applicationRef.tick`. Conferir um exemplo.
React
Utilize `flushSync`, porém tome cuidado com várias promises retornadas. Conferir um exemplo.

Aplicando interfaces no Typescript 😎

Como é um recurso recente (hoje 2023-08-03), a maioria dos editores não reconhecem a API. Então, caso você utilize o Typescript, basta adicionar o código abaixo em seu global.d.ts (ou informe o arquivo de tipo em compilerOptions.typeRoots no tsconfig.json)

export {};

interface ViewTransition {
  /**
   * A promise that fulfills when the promise returned by updateCallback fulfills,
     * or rejects when it rejects.
   */
  readonly updateCallbackDone: Promise<undefined>,

  /**
   * A promise that fulfills once the pseudo-elements for the transition are created,
   * and the animation is about to start.
   * It rejects if the transition cannot begin. This can be due to misconfiguration,
   * such as duplicate 'view-transition-name’s, or if updateCallbackDone returns a
     * rejected promise.
   * The point that ready fulfills is the ideal opportunity to animate the view
     * transition pseudo-elements with the Web Animation API.
   */
  readonly ready: Promise<undefined>,

  /**
   * A promise that fulfills once the end state is fully visible and interactive to
     * the user.
   * It only rejects if updateCallback returns a rejected promise, as this indicates
     * the end state wasn’t created.
   * Otherwise, if a transition fails to begin, or is skipped (by skipTransition()),
   * the end state is still reached, so finished fulfills.
   */
  readonly finished: Promise<undefined>,

  /**
   * Immediately finish the transition, or prevent it starting.
   * This never prevents updateCallback being called, as the DOM change
   * is independent of the transition
   * If this is called before ready resolves, ready will reject.
   * If finished hasn’t resolved, it will fulfill or reject along with
   * updateCallbackDone.
   */
  skipTransition: () => void,
}

type UpdateCallback = null|Promise<any>;

declare global {
  interface Document {
    startViewTransition: (updateCallback: UpdateCallback) => ViewTransition;
  }
}
Enter fullscreen mode Exit fullscreen mode

Compatibilidade


Referências

CHROMIUM. Blink: renderer/core/view_transition. GitHub, c2021. Disponível em: <https://github.com/chromium/chromium/tree/4817833c534a72d50606e5f97d1e003f5885494d/third_party/blink/renderer/core/view_transition>. Acesso em: 05 ago. 2023.

W3C. CSS View Transitions Module Level 1. W3C, 2012. Disponível em: <https://www.w3.org/TR/css-view-transitions-1/>. Acesso em: 05 ago. 2023.

WICG. View Transitions Explainer. GitHub, 2021. Disponível em: <https://github.com/WICG/view-transitions/blob/main/explainer.md>. Acesso em: 05 ago. 2023.

CAN I USE. View Transitions. Can I Use, [s.d.]. Disponível em: <https://caniuse.com/view-transitions>. Acesso em: 05 ago. 2023.


FIM!! 🎆

Homem soltando fogos de artifício porque que você chegou ao fim

Curtiu?? Não curtiu?? Tem alguma dúvida sobre o assunto?

Deixe um comentário para eu saber mais.

Espero que isso tenha sido útil para você. 😊

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay