Codelab: como criar um componente de navegação lateral

Este codelab ensina a criar um componente de layout responsivo de navegação lateral deslizável na Web. Criaremos o componente conforme avançamos, começando com HTML, CSS e JavaScript.

Confira minha postagem do blog Como criar um componente Sidenav (link em inglês) para saber mais sobre os recursos da plataforma da Web CSS escolhidos para criar esse componente.

Instalação

  1. Clique em Remixar para editar para tornar o projeto editável.
  2. Abra o app/index.html.

HTML

Primeiro, entenda o básico sobre a configuração de HTML para que haja conteúdo e algumas caixas para trabalhar.

Solte o seguinte HTML na tag <body>.

<aside></aside>
<main></main>

O <aside> contém o menu de navegação como um elemento complementar do <main>, que contém o conteúdo da página principal.

Em seguida, preencheremos esses elementos semânticos com o restante do conteúdo da página.

Adicione um elemento de navegação, alguns links de navegação e um link para fechar dentro do elemento <aside>.

<aside>
  <nav>
    <h4>My</h4>
    <a href="#">Dashboard</a>
    <a href="#">Profile</a>
    <a href="#">Preferences</a>
    <a href="#">Archive</a>

    <h4>Settings</h4>
    <a href="#">Accessibility</a>
    <a href="#">Theme</a>
    <a href="#">Admin</a>
  </nav>

  <a href="#"></a>
</aside>

Os links funcionam muito bem dentro de elementos <nav>, e os elementos <nav> funcionam muito bem nas barras laterais do <aside>. Ainda há muito o que fazer para melhorar.

No elemento de conteúdo principal, adicione um cabeçalho e um artigo para armazenar semanticamente o conteúdo do layout.

<main>
  <header>
    <a href="#sidenav-open" class="hamburger">
      <svg viewBox="0 0 50 40">
        <line x1="0" x2="100%" y1="10%" y2="10%" />
        <line x1="0" x2="100%" y1="50%" y2="50%" />
        <line x1="0" x2="100%" y1="90%" y2="90%" />
      </svg>
    </a>
    <h1>Site Title</h1>
  </header>

  <article>
    {put some placeholder content here}
  </article>
</main>

O cabeçalho tem o link de menu aberto. O lado direito tem o botão Fechar. Em breve, mostraremos e ocultaremos elementos com base no tamanho da janela de visualização.

No elemento <article>, colamos uma frase de marcador de posição. Substitua `` pelo seu conteúdo ou cole o texto abaixo:

<h2>Totam Header</h2>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Cum consectetur, necessitatibus velit officia ut impedit veritatis temporibus soluta? Totam odit cupiditate facilis nisi sunt hic necessitatibus voluptatem nihil doloribus! Enim.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead Totam Odit</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

Esse conteúdo e seu comprimento são o que fará com que a página seja rolável quando exceder a altura da janela de visualização.

Até agora, você adicionou um elemento aside, com uma navegação, links e uma forma de fechar a navegação lateral. Você também adicionou um cabeçalho, uma maneira de abrir a navegação lateral e um artigo para o elemento principal. Isso já é limpo, semântico e bastante atemporal, mas podemos torná-lo mais limpo e claro para todos. O link aberto na navegação lateral poderia ser marcado com mais clareza.

Adicione os atributos title e aria-label ao elemento de link aberto do cabeçalho:

<a href="#sidenav-open" class="hamburger">
<a href="#sidenav-open" title="Open Menu" aria-label="Open Menu" class="hamburger">

O ícone SVG aberto também pode ser marcado com mais clareza. Adicione os seguintes atributos ao SVG dentro do elemento de link aberto:

<svg viewBox="0 0 50 40">
<svg viewBox="0 0 50 40" role="presentation" focusable="false" aria-label="trigram for heaven symbol">

O link de fechamento na navegação lateral poderia ser marcado com mais clareza. Adicione os atributos title e aria-label ao elemento do link de fechamento lateral:

<a href="#"></a>
<a href="#" title="Close Menu" aria-label="Close Menu"></a>

CSS

É hora de definir o layout dos elementos. O conteúdo principal e a navegação lateral são filhos diretos da tag <body>, então esse é um bom ponto de partida.

Adicione o seguinte CSS ao css/sidenav.css para que o elemento <body> disponha os filhos.

body {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;

  @media (max-width: 540px) {
    & > :matches(aside, main) {
      grid-area: stack;
    }
  }
}

Esse layout diz basicamente: crie uma linha nomeada stack com tudo que ela contém e duas colunas nessa linha, sendo que a segunda também tem o nome stack. A 1a coluna deve ser dimensionada de acordo com suas necessidades mínimas de conteúdo, e a 2a coluna pode ocupar o restante. Em seguida, se em uma janela de visualização restrita de 540px ou menos, coloque a navegação lateral e os principais elementos de conteúdo na mesma linha e coluna, fazendo com que eles fiquem sobrepostos uns aos outros em uma grade de 1x1.

Com essa funcionalidade de empilhamento responsivo como base, agora podemos aproveitar o estado da barra de URL para alternar a visibilidade e o estilo de transição da navegação lateral.

Atualize o elemento <aside> novamente em app/index.html:

<aside>
<aside id="sidenav-open">

Isso permite que o CSS faça a correspondência entre um elemento e o hash do URL. Isso é importante para o uso do :target. Agora o ID do elemento pode corresponder ao hash do URL que vamos definir com as tags <a>.

Além disso, para facilitar a segmentação do JavaScript, adicione IDs para elementos-chave que controlam a navegação lateral. Primeiro, adicione um ID ao link aberto de navegação lateral:

<a href="#sidenav-open" class="hamburger" title="Open Menu" aria-label="Open Menu">
<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">

Em seguida, adicione um ID ao link de fechamento lateral:

<a href="#" title="Close Menu" aria-label="Close Menu"></a>
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

Isso encerra o layout de empilhamento responsivo da macro <body> e nos prende à barra de URL. Vamos continuar.

O <aside> também tem um layout interessante. Ele tem dois filhos: um <nav>, que é o componente parecido com papel que desliza para fora, e um elemento do link <a> de fechamento, que define o URL como #. O link é invisível à direita da navegação de deslize de papel, para que as pessoas possam "clicar" no componente visual para dispensá-lo.

Adicione o seguinte CSS a css/sidenav.css:

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

Achei a proporção e os nomes muito legais aqui, onde a grade poderia brilhar e dar muito controle ao designer.

Em seguida, preciso sobrepor condicionalmente o conteúdo principal e manter minha posição durante a rolagem de qualquer documento. Esse é um ótimo trabalho para position: sticky e alguns overscroll-behavior.

Adicione estes estilos à navegação lateral:

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;

  @media (max-width: 540px) {
    position: sticky;
    top: 0;
    max-height: 100vh;
    overflow: hidden auto;
    overscroll-behavior: contain;

    visibility: hidden; /* not keyboard accessible when closed */
  }
}

Esses estilos garantem que a navegação lateral seja a altura da janela de visualização, rola a tela verticalmente e contenha a rolagem. E o mais importante: ela oculta o elemento. Por padrão, quando a janela de visualização for 540px ou menor, oculte essa navegação lateral. A não ser!

Adicione um pseudosseletor :target ao elemento #sidenav-open:

#sidenav-open {

  @media (max-width: 540px) {

    &:target {
      visibility: visible;
    }
  }
}

Quando o ID desse elemento e a barra de URL forem os mesmos, defina visibility como visible. Vá em frente e abra o menu lateral depois de rolar a página ou tente rolar a página enquanto a navegação lateral está aberta. O que você acha?

Adicione o seguinte CSS à parte inferior de app/sidenav.css:

#sidenav-button,
#sidenav-close {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  user-select: none;
  touch-action: manipulation;

  @media (min-width: 540px) {
    display: none;
  }
}

Esses estilos segmentam nossos botões de abrir e fechar, especificam os estilos de toque e toque, além de ocultar quando as janelas de visualização são 540px ou maiores.

Para um pouco de estilo, vamos adicionar transformações CSS com acessibilidade respeitosa. Adicione o seguinte CSS a css/sidenav.css:

#sidenav-open {
  --easeOutExpo: cubic-bezier(0.16, 1, 0.3, 1);
  --duration: .6s;

  ...

  @media (max-width: 540px) {
    ...

    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);

    &:target {
      visibility: visible;
      transform: translateX(0);
      transition: transform var(--duration) var(--easeOutExpo);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    --duration: 1ms;
  }
}
Uma demonstração da interação com e sem duração aplicada com base na consulta de mídia "prefers-reduced-motion".

Use um pouco de JavaScript

A tecla Escape vai fechar o menu. Adicione este JS a js/index.js:

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', e => {
  if (e.code === 'Escape') {
    document.location.hash = '';
  }
});

Detecta um evento de tecla no elemento de navegação lateral. Se for Escape, ele vai definir o hash do URL como vazio, fazendo a transição lateral de navegação.

A próxima parte de UX de UX é o gerenciamento de foco. Quero facilitar a abertura e o fechamento, então espero até que o sidenav termine uma transição de algum tipo e, em seguida, verifique-a com o hash do URL para determinar se ele está dentro ou fora. Depois, uso o JavaScript para definir o foco do botão complementar para o que eles acabaram de pressionar.

Adicione o seguinte JavaScript a js/index.js:

const closenav = document.querySelector('#sidenav-close');
const opennav = document.querySelector('#sidenav-button');

sidenav.addEventListener('transitionend', e => {
  if (e.propertyName !== 'transform') {
    return;
  }

  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
    ? closenav.focus()
    : opennav.focus();
});

Testar

  • Para visualizar o site, pressione Ver app. Em seguida, pressione Tela cheia modo tela cheia.

Conclusão

Esse é o resumo sobre as necessidades que tive com o componente. Fique à vontade para criar a partir dele, guiá-lo com o estado JavaScript em vez do URL e, em geral, torná-lo seu. Sempre há mais para adicionar ou mais casos de uso para abordar.

Abra css/brandnav.css para conferir os estilos não relacionados ao layout que apliquei a este componente. Não achei que fosse importante para o conjunto de recursos em que estava me concentrando e esperava que a separação de estilos do layout incentivasse o recurso de copiar e colar. Pode haver mais aprendizado para você lá!

Como criar componentes de navegação lateral responsivos? Você tem mais de um item, como um dos dois lados? Seria ótimo apresentar sua solução em um vídeo do YouTube. Não se esqueça de twittar para mim ou comentar no YouTube com seu código. Isso ajudará a todos.