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

Este codelab ensina a criar um componente de layout de navegação lateral responsivo na Web. Vamos criar o componente à medida que avançamos, começando com HTML, depois CSS e JavaScript.

Confira minha postagem no blog Como criar um componente de barra lateral para saber mais sobre os recursos da plataforma da Web CSS escolhidos para criar esse componente.

Configuração

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

HTML

Primeiro, confira os elementos essenciais da configuração HTML para ter 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 ao <main>, que contém o conteúdo principal da página.

Em seguida, vamos preencher 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 de fechamento 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 ficam ótimos dentro dos elementos <nav>, e os elementos <nav> ficam ótimos nas barras laterais <aside>. No entanto, ainda podemos melhorar.

No elemento de conteúdo principal, adicione um cabeçalho e um artigo para manter 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 para abrir o menu. O aside tem o botão "Fechar". Em breve, vamos mostrar e ocultar elementos com base no tamanho da janela de visualização.

No elemento <article>, colamos uma frase de marcador de posição. Substitua `` pelo seu próprio conteúdo ou cole o lorem fornecido 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 o comprimento dele fazem com que a página possa ser rolada quando excede a altura da janela de visualização.

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

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 poderia ser marcado de forma mais clara. 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 de forma mais clara. Adicione os atributos title e aria-label ao elemento de link de fechamento da barra lateral:

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

CSS

É hora de criar o layout dos elementos. O conteúdo principal e a barra de navegação lateral são filhos diretos da tag <body>. Portanto, esse é um bom lugar para começar.

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

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

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

Esse layout essencialmente diz: crie uma linha nomeada stack com tudo nela e duas colunas nessa linha, a segunda das quais também é chamada de stack. A primeira coluna deve ser dimensionada de acordo com as necessidades mínimas de conteúdo, e a segunda pode ocupar o restante. Em seguida, se estiver em uma janela de visualização restrita de 540px ou menos, coloque os elementos de navegação lateral e conteúdo principal na mesma linha e coluna, resultando em um posicionamento um em cima do outro em uma grade 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 barra lateral.

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

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

Isso permite que o CSS corresponda a um elemento e ao hash de URL juntos. Isso é importante para o uso do :target. Agora o ID do elemento pode corresponder ao hash do URL que vamos definir com tags <a>.

Além disso, para facilitar a segmentação por JavaScript, adicione IDs aos elementos principais que controlam a barra de navegação lateral. Primeiro, adicione um ID ao link de abertura da barra 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 da barra 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 conclui o layout responsivo de empilhamento da macro <body> e nos conecta à barra de URL. Vamos continuar.

O <aside> também tem um layout organizado. Ele tem dois filhos: um <nav>, que é o componente deslizante com aparência de papel, e um elemento de link <a> de fechamento que define o URL como #. O link fica invisível à direita da navegação deslizante de papel. Assim, as pessoas podem "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 pode brilhar e dar muito controle a um designer.

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

Adicione os seguintes estilos para a barra de 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 barra lateral seja a altura da janela de visualização, role verticalmente e contenha a rolagem. E o mais importante: ele oculta o elemento. Por padrão, quando a janela de visualização é 540px ou menor, a barra lateral é ocultada. A não ser que…

Adicione um pseudoseletor :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 iguais, defina visibility como visible. 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 de baixo 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 têm como destino nossos botões de abrir e fechar, especificam os estilos de toque e toque, e também os ocultam quando os viewports são 540px ou maiores.

Para dar um toque especial, vamos adicionar transformações de CSS com acessibilidade adequada. 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".

Adicionar um pouco de JavaScript

A tecla Escape fecha 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 = '';
  }
});

Isso detecta um evento de tecla no elemento da barra lateral. Se for Escape, o hash do URL será definido como vazio, fazendo com que a navegação lateral desapareça.

A próxima parte do UX JS é o gerenciamento de foco. Quero facilitar a abertura e o fechamento, então espero até que a navegação lateral termine uma transição de algum tipo e faço uma verificação cruzada com o hash do URL para determinar se ele está dentro ou fora. Em seguida, uso JavaScript para definir o foco no botão complementar ao que acabou de ser pressionado.

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();
});

Faça um teste

  • Para visualizar o site, pressione Ver app e depois Tela cheia tela cheia.

Conclusão

Isso é tudo que eu precisava saber sobre o componente. Fique à vontade para criar com base nele, use o estado do JavaScript em vez do URL e, em geral, faça o que quiser! Sempre há mais para adicionar ou mais casos de uso para cobrir.

Abra css/brandnav.css para conferir os estilos não relacionados ao layout que apliquei a esse 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 e layout incentivasse a cópia e colagem. Talvez você encontre mais conteúdo para aprender!

Como criar componentes responsivos de sidenav que deslizam para fora? Você já teve mais de um, como um em cada lado? Adoraria mostrar sua solução em um vídeo do YouTube. Envie um tweet para mim ou deixe um comentário no YouTube com seu código. Isso vai ajudar todo mundo.