Experiência ativa com portais: navegação perfeita na Web

Saiba como a API Portals proposta pode melhorar a UX de navegação.

Yusuke Utsunomiya
Yusuke Utsunomiya

Garantir que as páginas carreguem rapidamente é fundamental para oferecer uma boa experiência do usuário. No entanto, uma área que muitas vezes deixamos de lado são as transições de página, ou seja, o que nossos usuários veem quando se movem entre as páginas.

Uma nova proposta de API da plataforma da Web chamada Portals tem como objetivo ajudar a simplificar a experiência enquanto os usuários navegam pelo seu site.

Confira os portais em ação:

Integração e navegação perfeitas com os portais. Criado por Adam Argyle.

O que os portais permitem

Os aplicativos de página única (SPAs) oferecem transições legais, mas têm o custo de maior complexidade de criação. Os aplicativos de várias páginas (MPAs, na sigla em inglês) são muito mais fáceis de criar, mas você acaba com telas em branco entre as páginas.

Os portais oferecem o melhor dos dois mundos: a baixa complexidade de um MPA com as transições contínuas de um SPA. Pense nelas como um <iframe>, que permite a incorporação, mas, ao contrário de um <iframe>, elas também têm recursos para navegar pelo conteúdo.

Ver para crer: confira o que mostramos no Chrome Dev Summit 2018:

Com a navegação clássica, os usuários precisam esperar com uma tela em branco até que o navegador termine de renderizar o destino. Com os portais, os usuários podem conferir uma animação, enquanto o <portal> renderiza o conteúdo e cria uma experiência de navegação perfeita.

Antes dos portais, poderíamos renderizar outra página usando um <iframe>. Também poderíamos adicionar animações para mover o frame pela página. No entanto, um <iframe> não permite navegar pelo conteúdo. Os portais eliminam essa lacuna, permitindo casos de uso interessantes.

Testar os portais

Ativação por about://flags

Teste os portais no Chrome 85 e versões mais recentes ativando uma flag experimental:

  • Ative a flag about://flags/#enable-portals para navegações de mesma origem.
  • Para testar navegações entre origens, ative também a flag about://flags/#enable-portals-cross-origin.

Durante essa fase inicial do experimento do Portals, recomendamos também usar um diretório de dados do usuário completamente separado para seus testes definindo a flag de linha de comando --user-data-dir. Depois que os portais forem ativados, confirme no DevTools se você tem o novo HTMLPortalElement.

Uma captura de tela do console do DevTools mostrando o HTMLPortalElement

Implementar portais

Vamos conferir um exemplo básico de implementação.

// Create a portal with the wikipedia page, and embed it
// (like an iframe). You can also use the <portal> tag instead.
portal = document.createElement('portal');
portal.src = 'https://en.wikipedia.org/wiki/World_Wide_Web';
portal.style = '...';
document.body.appendChild(portal);

// When the user touches the preview (embedded portal):
// do fancy animation, e.g. expand …
// and finish by doing the actual transition.
// For the sake of simplicity, this snippet will navigate
// on the `onload` event of the Portals element.
portal.addEventListener('load', (evt) => {
   portal.activate();
});

É simples assim. Teste este código no console do DevTools. A página da Wikipedia vai ser aberta.

Um GIF de demonstração do estilo do portal de visualização

Se você quiser criar algo como o que mostramos na Chrome Dev Summit, que funciona exatamente como a demonstração acima, o snippet a seguir será útil.

// Adding some styles with transitions
const style = document.createElement('style');
style.innerHTML = `
  portal {
    position:fixed;
    width: 100%;
    height: 100%;
    opacity: 0;
    box-shadow: 0 0 20px 10px #999;
    transform: scale(0.4);
    transform-origin: bottom left;
    bottom: 20px;
    left: 20px;
    animation-name: fade-in;
    animation-duration: 1s;
    animation-delay: 2s;
    animation-fill-mode: forwards;
  }
  .portal-transition {
    transition: transform 0.4s;
  }
  @media (prefers-reduced-motion: reduce) {
    .portal-transition {
      transition: transform 0.001s;
    }
  }
  .portal-reveal {
    transform: scale(1.0) translateX(-20px) translateY(20px);
  }
  @keyframes fade-in {
    0%   { opacity: 0; }
    100% { opacity: 1; }
  }
`;
const portal = document.createElement('portal');
// Let's navigate into the WICG Portals spec page
portal.src = 'https://wicg.github.io/portals/';
// Add a class that defines the transition. Consider using
// `prefers-reduced-motion` media query to control the animation.
// https://developers.google.com/web/updates/2019/03/prefers-reduced-motion
portal.classList.add('portal-transition');
portal.addEventListener('click', (evt) => {
  // Animate the portal once user interacts
  portal.classList.add('portal-reveal');
});
portal.addEventListener('transitionend', (evt) => {
  if (evt.propertyName == 'transform') {
    // Activate the portal once the transition has completed
    portal.activate();
  }
});
document.body.append(style, portal);

Também é fácil fazer a detecção de recursos para aprimorar progressivamente um site usando os portais.

if ('HTMLPortalElement' in window) {
  // If this is a platform that have Portals...
  const portal = document.createElement('portal');
  ...
}

Se você quiser conferir rapidamente como os portais funcionam, use uskay-portals-demo.glitch.me. Acesse com o Chrome 85 ou versões mais recentes e ative a flag experimental.

  1. Insira um URL que você quer visualizar.
  2. A página será incorporada como um elemento <portal>.
  3. Clique na visualização.
  4. A visualização será ativada após uma animação.

GIF da demonstração de uso do Portal com falha

Confira a especificação

Estamos discutindo ativamente a especificação de portais no grupo da comunidade de incubação da Web (WICG, na sigla em inglês). Para se familiarizar rapidamente, confira alguns dos principais cenários. Estes são os três recursos importantes que você precisa conhecer:

  • O elemento <portal>:o elemento HTML em si. A API é muito simples. Ele consiste no atributo src, na função activate e em uma interface para mensagens (postMessage). activate recebe um argumento opcional para transmitir dados ao <portal> após a ativação.
  • A interface portalHost:adiciona um objeto portalHost ao objeto window. Isso permite verificar se a página está incorporada como um elemento <portal>. Ele também fornece uma interface para mensagens (postMessage) de volta ao host.
  • A interface PortalActivateEvent:um evento que é acionado quando o <portal> é ativado. Há uma função interessante chamada adoptPredecessor, que pode ser usada para recuperar a página anterior como um elemento <portal>. Isso permite criar navegações e experiências compostas perfeitas entre duas páginas.

Vamos olhar além do padrão de uso básico. Confira uma lista não exaustiva do que você pode fazer com os portais e um exemplo de código.

Personalizar o estilo quando incorporado como um elemento <portal>

// Detect whether this page is hosted in a portal
if (window.portalHost) {
  // Customize the UI when being embedded as a portal
}

Mensagens entre o elemento <portal> e portalHost

// Send message to the portal element
const portal = document.querySelector('portal');
portal.postMessage({someKey: someValue}, ORIGIN);

// Receive message via window.portalHost
window.portalHost.addEventListener('message', (evt) => {
  const data = evt.data.someKey;
  // handle the event
});

Ativar o elemento <portal> e receber o evento portalactivate

// You can optionally add data to the argument of the activate function
portal.activate({data: {somekey: 'somevalue'}});

// The portal content will receive the portalactivate event
// when the activate happens
window.addEventListener('portalactivate', (evt) => {
  // Data available as evt.data
  const data = evt.data;
});

Como recuperar o antecessor

// Listen to the portalactivate event
window.addEventListener('portalactivate', (evt) => {
  // ... and creatively use the predecessor
  const portal = evt.adoptPredecessor();
  document.querySelector('someElm').appendChild(portal);
});

Saber que sua página foi adotada como antecessora

// The activate function returns a Promise.
// When the promise resolves, it means that the portal has been activated.
// If this document was adopted by it, then window.portalHost will exist.
portal.activate().then(() => {
  // Check if this document was adopted into a portal element.
  if (window.portalHost) {
    // You can start communicating with the portal element
    // i.e. listen to messages
    window.portalHost.addEventListener('message', (evt) => {
      // handle the event
    });
  }
});

Ao combinar todos os recursos compatíveis com os Portais, é possível criar experiências do usuário realmente sofisticadas. Por exemplo, a demonstração abaixo mostra como os Portais podem oferecer uma experiência de usuário perfeita entre um site e o conteúdo incorporado de terceiros.

Casos de uso e planos

Esperamos que você tenha gostado deste breve tour pelos Portais. Mal podemos esperar para ver o que você vai criar. Por exemplo, você pode começar a usar os portais para navegações não triviais, como: pré-renderizar a página do seu produto mais vendido em uma página de listagem de categoria de produtos.

Outra informação importante é que os portais podem ser usados em navegações entre origens, assim como um <iframe>. Portanto, se você tiver vários sites que se referem uns aos outros, também poderá usar os portais para criar navegações perfeitas entre dois sites diferentes. Esse caso de uso entre origens é muito específico dos portais e pode até melhorar a experiência do usuário das SPAs.

O feedback é bem-vindo

Os portais estão prontos para experimentação no Chrome 85 e versões mais recentes. O feedback da comunidade é crucial para o design de novas APIs. Teste e nos conte o que achou. Se você tiver solicitações de recursos ou feedback, acesse o repositório do GitHub do WICG.