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 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 Remixar para editar 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.

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 mantém o conteúdo da página principal.

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 de menu aberto. O lado 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 fazem com que a página seja rolável quando excede 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 o menu 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 sidenav:

<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 responsiva 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> 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 do :target. Agora, o ID do elemento pode corresponder ao hash de 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 de abertura 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 é invisível à direita da navegação de saída do 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 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 a 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 tenha a altura da janela de visualização, role verticalmente e contenha a rolagem. E, 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 menos que!

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

Ele detecta um evento de tecla no elemento sidenav. Se for Escape, ele definirá o hash do URL como vazio, fazendo a transição da navegação secundária.

A próxima parte do JS 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, 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. Sinta-se à vontade para criar com base nele, orientá-lo com o estado do JavaScript em vez do URL e, em geral, torná-lo seu. 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ê faz componentes de sidenav responsivos deslizantes? Você tem mais de um, por exemplo, um de 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.