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, por fim, JavaScript.

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

Configuração

  1. Clique em Remix to Edit para tornar o projeto editável.
  2. Abra app/index.html.

HTML

Primeiro, entenda os fundamentos da configuração de HTML para que haja conteúdo e algumas caixas para trabalhar.

Coloque 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 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 de elementos <nav>, e os elementos <nav> ficam ótimos nas barras laterais <aside>. 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 painel lateral tem o botão "Fechar". Em breve, vamos mostrar e ocultar elementos com base no tamanho da viewport.

No elemento <article>, colamos uma frase de marcador de posição. Substitua `` pelo seu conteúdo ou cole o lorem 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 são o que vão fazer com que a página seja rolável quando exceder a altura da janela de visualização.

Até agora, você adicionou um elemento de lado, com um menu de navegação, links e uma maneira de fechar o sidenav. Você também adicionou um cabeçalho, uma maneira de abrir a barra lateral e um artigo ao elemento principal. O texto é claro, semântico e atemporal, mas podemos torná-lo mais limpo e claro para todos. O link aberto no sidenav 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 no menu lateral poderia ser marcado de forma mais clara. Adicione os atributos title e aria-label ao elemento de link de fechamento do menu lateral:

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

CSS

É hora de organizar os elementos. O conteúdo principal e o sidenav são filhos diretos da tag <body>. Portanto, esse é um bom lugar para começar.

Adicione o CSS a seguir 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 basicamente diz: crie uma linha nomeada stack com tudo nela, e duas colunas nessa linha, sendo que a segunda também é nomeada stack. A primeira coluna deve ser dimensionada de acordo com as necessidades mínimas de conteúdo, e a segunda coluna pode ocupar o restante. Em seguida, se estiver em uma janela de visualização restrita de 540px ou menos, coloque a navegação lateral e os elementos do conteúdo principal na mesma linha e coluna, fazendo com que eles fiquem 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 do sidenav.

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

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

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

Além disso, para facilitar o direcionamento do JavaScript, adicione IDs para os principais elementos que controlam o menu lateral. Primeiro, adicione um ID ao link aberto do menu 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 do sidenav:

<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 <body> macro 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 semelhante a papel que desliza para fora, e um elemento de link <a> de fechamento que define o URL como #. O link está invisível à direita da navegação deslizante de papel. Isso é para que as pessoas possam "desmarcar" o 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 que a proporção e os nomes foram um toque muito legal, em que a grade poderia brilhar e dar ao designer muito controle.

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

Adicione os seguintes estilos para o sidenav:

#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 o sidenav seja a altura da viewport, role verticalmente e contenha a rolagem. Ele também oculta o elemento. Por padrão, quando a janela de visualização é 540px ou menor, ela oculta o menu lateral. A menos que!

Adicione um pseudo-seletor :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. Vá em frente e abra o menu lateral depois de rolar a página ou tente rolar a página enquanto o sidenav está aberto. 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 são direcionados aos botões de abrir e fechar, especificam os estilos de toque e toque, e também os ocultam quando as viewports são 540px ou maiores.

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

Adicione 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 = '';
  }
});

Ele detecta um evento de tecla no elemento sidenav. Se for Escape, o hash do URL será definido como vazio, fazendo a transição do sidenav.

A próxima parte do UX JS é 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, verifico se ele corresponde ao hash do URL para determinar se ele está dentro ou fora. Em seguida, uso o JavaScript para definir o foco no botão complementar ao que o usuário acabou 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();
});

Faça um teste

  • Para visualizar o site, pressione View App. Em seguida, pressione Fullscreen tela cheia.

Conclusão

Esse é o resumo das necessidades que eu tinha com o componente. Fique à vontade para fazer melhorias, controlar com o estado do JavaScript em vez do URL e, em geral, fazer 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 este componente. Não achei que isso fosse importante para o conjunto de recursos em que estava me concentrando e esperava que separar os estilos do layout incentivasse a cópia e a colagem. Talvez você aprenda mais por lá.

Como você cria componentes de sidenav responsivos deslizantes? Você já teve mais de um, como um em ambos os lados? Gostaria de mostrar sua solução em um vídeo do YouTube. Envie um tweet para mim ou comente no YouTube com seu código. Isso vai ajudar a todos.