Menu de navegação responsivo e animado usando só HTML, CSS e um pouquinho de Javascript
Tags: tutorial, frontend, beginners, braziliandevs
Published: 2024-03-13 14:09:45

O título é bem auto descritivo, então vamos direto ao ponto. Para começar, esse é o nosso HTML, nada muito diferente do que você vê no dia a dia.

## HTML

```html
<!DOCTYPE html>
<html lang="pt-br">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Menu de navegação</title>

    <link rel="stylesheet" href="style.css" />
    <script src="script.js" defer></script>
  </head>
  <body>
    <header>
      <h1>Meu site</h1>
      <button class="btn-hamburger" type="button" title="Fechar">
        <i></i>
        <i></i>
        <i></i>
      </button>
      <nav>
        <ul>
          <li class="close-button">
            <button class="btn-hamburger" type="button" title="Fechar">
              <i></i>
              <i></i>
              <i></i>
            </button>
          </li>
          <li><a href="#">Home</a></li>
          <li><a href="#">Sobre</a></li>
          <li><a href="#">Contato</a></li>
        </ul>
      </nav>
    </header>
  </body>
</html>
```

No head temos apenas algumas tags definindo o titulo da página e linkando os nossos futuros arquivos `style.css` e `script.js`.

Já no body nós temos um header que contem o título do site, o nosso botão de menu, e uma tag `nav` que vai ser nossa navbar. As tags `<i></i>` dentro do botão de menu serão as responsáveis por formar os ícones de menu hambúrguer e de X no nosso botão, mas não vamos nos adiantar, já já falamos disso.

Dentro da nossa tag nav nós temos uma lista com os links de navegação para as outras páginas, e você deve ter reparado que o primeiro item da lista tem a mesma estrutura do botão de menu do header.

Não é erro de digitação, estamos repetindo ele dentro da navbar para que possamos fechar o menu quando ele estiver aberto, já que a ideia é que o menu sobreponha toda a página em telas pequenas, o que impede a gente de clicar naquele nosso botão do header para acionar o fechamento.

## CSS

Passando para o CSS, vamos em partes, primeiro vamos definir algumas variáveis para deixar a nossa vida mais simples no futuro e dar um reset simples na página para não haver discrepância entre os navegadores:

```css
/* style.css */
:root {
  --bg: #222823; /* Cor de fundo do menu */
  --fg: #575a5e; /* Cor de destaque */
  --hamburger-btn-padding: 8px; /* Padding do botão de menu */
  --hamburger-btn-size: 32px; /* Tamanho do botão de menu */
  --hamburger-btn-gap: 4px; /* Espaçamento das linhas do botão de menu */
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: sans-serif;
}
```

Em seguida vamos customizar os botões da nossa página, apenas para deixar eles mais agradáveis:

```css
/* style.css */
button {
  border-radius: 4px;
  border: none;
  background-color: var(--bg);
  transition: filter 300ms ease-out;
  cursor: pointer;
}

button:hover {
  filter: brightness(1.2);
}
```

Agora vamos estilizar nosso header, para que possamos diferenciá-lo do resto da página:

```css
/* style.css */
header {
  display: flex;
  justify-content: space-between;
  padding: 16px;
  background-color: var(--bg);
  color: #fff;
  align-items: center;
}
```

Aqui eu estou aplicando o `display: flex;` com `justify-content: space-between;` para fazer com que o título fique do lado esquerdo e o botão de menu/navbar vá para o lado direito da tela.

No mais eu estou apenas aplicando um `padding` para dar um espaço entre as bordas e um `align-items: center` para deixar a o título e o botão/navbar alinhados.

Como eu estou usando uma cor de fundo escura, estou deixando a cor do texto branca para conseguir ler, caso você tenha usado uma cor clara para o fundo e esteja ruim de ler, basta alterar essa linha para melhorar a visibilidade do texto.

Agora vamos passar para o botão do menu.

```css
/* style.css */
.btn-hamburger {
  width: var(--hamburger-btn-size);
  height: var(--hamburger-btn-size);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--hamburger-btn-gap);
  flex-direction: column;
}
```

A classe `btn-hamgurger` vai ser a responsável por identificar e estilizar os nossos botões de menu na página.

As customizações aqui consistem basicamente na definição da largura e da altura do botão. Além da definição da posição como `relative` que nos vai ser útil na hora de animar o ícone do botão.

As demais mudanças servem apenas para manter as três tags `<i></i>` centralizadas no botão e espaçadas igualmente umas das outras.

Agora é importante mencionar que o que irá definir se o menu está aberto ou não é a presença da classe `active`. No botão essa classe será a responsável por estilizar as três tags `i` como um X ao invés de um hambúrguer. Já na nav ela será a responsável por mostrar o menu em aparelhos com telas pequenas.

Estilizamos as tags i assim para que elas fiquem parecendo com as barras de um botão de menu:

```css
/* style.css */

.btn-hamburger i {
  height: 2px;
  width: calc(var(--hamburger-btn-size) - 2 * var(--hamburger-btn-padding));
  background-color: var(--fg);
  border-radius: 4px;
  transition: transform 150ms ease-in-out;
}

.btn-hamburger.active i {
  position: absolute;
}

.btn-hamburger.active i {
  transform: rotate(45deg);
}

.btn-hamburger.active i:nth-of-type(2) {
  transform: rotate(-45deg);
}
```

O primeiro bloco de código apenas define a altura de cada barra e realiza um cálculo simples para definir a largura da barra como sendo a largura total do botão menos o `padding` aplicado. O `transition `está ali apenas para que a mudança de ícone ocorra de forma gradual como uma animação, ao invés de instantânea.

O segundo bloco de código muda o `position` dessas tags para `absolute` a fim de que elas consigam sobrepor umas as outras para formar o ícone de X.

Para transformar as três barras em um X, precisamos apenas rotacionar duas delas em 45 graus, e a outra em -45 graus, que é o que estamos fazendo no terceiro e quarto blocos de código.

Já para a navbar aplicaremos essas propriedades:

```css
/* style.css */
nav {
  width: 100vw;
  min-height: 100dvh;
  background-color: var(--bg);
  position: absolute;
  top: 0;
  left: 100vw;
  padding: 16px;
  transition: left 150ms ease-out;
}

nav.active {
  left: 0;
}

nav ul {
  display: flex;
  flex-direction: column;
  list-style: none;
  gap: 4px;
}

nav li.close-button {
  align-self: flex-end;
}

nav a {
  display: block;
  width: 100%;
  color: #fff;
  text-decoration: none;
  padding: 8px 12px;
  border-radius: 8px;
  transition: background 300ms ease-in-out;
}

nav a:hover {
  background: var(--fg);
}
```

Aqui vamos focar no primeiro e segundo blocos de código, já que os demais são apenas estilização.

```css
nav {
  width: 100vw;
  min-height: 100dvh;
  background-color: var(--bg);
  position: absolute;
  top: 0;
  left: 100vw;
  padding: 16px;
  transition: left 150ms ease-out;
}

nav.active {
  left: 0;
}
```

Nesse primeiro bloco estamos fazendo a navbar ocupar um espaço correspondente a tela toda e então estamos usando as propriedades de posicionamento (`position`, `left` e `top`) para fazer com que ela se desloque totalmente para a direita e saia da tela, a deixando fora de vista, já que a ausência da classe `active` indica que ela deve estar fechada.

Por outro lado, quando a classe está presente devemos conseguir ver a navbar, pois ela estaria aberta. Nesse caso a gente define `left: 0;` para fazer com que ela inicie do canto esquerdo da tela ao invés do direito e fique visível na tela.

Se você testar agora, vai que a animação até está funcionando, mas há dois problemas:

1. A navbar continua visível, estando apenas fastada para o lado criando uma rolagem horizontal tenebrosa na página toda

2. Não há responsividade: em telas grandes ela continua aparecendo como um menu lateral ao invés de uma navbar.

Então vamos resolver essas questões.

A primeira é bem simples, basta que mandemos o css esconder tudo que está para fora do body horiozntalmente:

```css
/* style.css */
body {
  font-family: sans-serif;
  overflow-x: hidden; /* Adicione essa linha */
}
```

Para o segundo problema uma media query é suficiente para resolver:

```css
/* style.css */
@media screen and (min-width: 768px) {
  .btn-hamburger {
    display: none;
  }

  nav {
    position: initial;
    min-height: auto;
    width: auto;
    background: none;
    padding: 0;
  }

  nav ul {
    flex-direction: row;
  }
}
```

Nesses blocos de código estamos fazendo o seguinte:

1. Escondendo os botões de menu em telas grandes

2. Removendo a estilização que faz com que a navbar ocupe toda a tela e seja deslocada para fora da visão

3. Alterando a distribuições dos links de vertical para horizontal

## Javascript

Por fim, agora temos que fazer os botões de menu provocarem as alterações de classe para que possamos abrir e fechar o menu:

```javascript
// script.js

const hamburgerButtons = document.querySelectorAll('.btn-hamburger');
const navbar = document.querySelector('nav');

function menuToggle() {
  hamburgerButtons.forEach((button) => button.classList.toggle('active'));
  navbar.classList.toggle('active');
}

hamburgerButtons.forEach((button) => {
  button.addEventListener('click', menuToggle);
});

```

## Resultado

Se você seguiu tudo até aqui sem se perder, você deve ter algo mais ou menos assim nas suas telas grandes:

![Página resultante do tutorial em tamanho desktop](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2om4c66xvfslidgy89w2.png)

E algo assim nas suas telas pequenas:

![Página resultante do tutorial em tamanho mobile, com menu fechado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k8vyci8isy5rr5d80i1z.png)

![Página resultante do tutorial em tamanho mobile, com menu aberto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rn6gcrto54j8r1dcq04q.png)

E é isso, espero que tenha gostado! 😉