Crie um componente suspenso reutilizável com JavaScript

Neste tutorial, criaremos um componente suspenso personalizado e reutilizável, usando JavaScript vanilla.

Um o quê? Uma estratégia para reduzir o tamanho de um menu ou navegação é colocar links adicionais dentro de um componente suspenso. A ideia é que os usuários cliquem para revelar mais opções, dando a eles mais maneiras de navegar nas páginas da web, mantendo a interface do usuário limpa.

Para este tutorial, presumirei que estamos construindo um componente para o site de uma agência de marketing.

Como construir um componente suspenso com JavaScript

Tudo bem, vamos ficar presos! Como de costume, começaremos com a marcação HTML para nossa estrutura, depois adicionaremos alguns estilos com CSS e, finalmente, adicionaremos o comportamento com JavaScript vanilla.

Estrutura HTML

Primeiro, a estrutura HTML. Usaremos uma lista não ordenada com itens de lista e links.

1
 role="primary navigation">
2
   href="#">Home
3
   class="dropdown">
4
     href="#" class="dropdown-action">
5
        Services
6
         class="dropdown-icon" xmlns="https://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"></span>chevron-down<span style="color: #bb0066;font-weight: bold"> fill="none"> stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.25 10.75L12 14.25l-3.25-3.5">
7
    
8
     class="dropdown-menu">
9
      
  • href="#">SEO
  • 10
          
  • href="#">Content Strategy
  • 11
          
  • href="#">Copywriting
  • 12
          
  • href="#">Storytelling
  • 13
        
    
    14
      
    
    15
       href="#">About
    
    16
       href="#">Contact
    
    17
       class="dropdown">
    
    18
         href="#" class="dropdown-action">
    
    19
            Account
    
    20
             class="dropdown-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"></span>chevron-down<span style="color: #bb0066;font-weight: bold"> fill="none"> stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.25 10.75L12 14.25l-3.25-3.5">
    
    21
        
    
    22
         class="dropdown-menu">
    
    23
          
  • href="#">Login
  • 24
          
  • href="#">Sign up
  • 25
        
    
    26
      
    
    27
    
    

    Observe os nomes das classes no link de âncora pai .dropdown-action e a lista não ordenada .dropdown-menu. Essas convenções de nomenclatura de classe são mais genéricas, portanto, podemos reutilizar o CSS e o JavaScript quantas vezes quisermos no site.

    Adicionando alguns estilos com CSS

    Vamos dar à lista suspensa alguns tratamentos estéticos com CSS.

    Lembre-se de que a base principal de um menu suspenso está ocultando e mostrando adequadamente a lista sob demanda. Vou projetar a lista suspensa com isso em mente.

    1
    nav {
    
    2
      width: 1020px;
    
    3
      display: flex;
    
    4
      align-items: center;
    
    5
      margin: 20px auto;
    
    6
      padding-bottom: 10px;
    
    7
      border-bottom: 1px solid #ddd;
    
    8
    }
    
    9
    10
    nav a {
    
    11
      padding: 10px 16px;
    
    12
      border-radius: 4px;
    
    13
      display: inline-block;
    
    14
      color: #334155;
    
    15
      text-decoration: none;
    
    16
    }
    
    17
    18
    nav a:hover {
    
    19
      color: #0ea5e9;
    
    20
      background-color: #f0f9ff;
    
    21
    }
    
    22
    23
    .dropdown {
    
    24
      position: relative;
    
    25
    }
    
    26
    27
    .dropdown-action {
    
    28
      display: flex;
    
    29
      align-items: center;
    
    30
      padding-right: 10px;
    
    31
    }
    
    32
    33
    .dropdown-icon {
    
    34
      stroke: currentColor;
    
    35
    }
    
    36
    37
    .dropdown-menu {
    
    38
      display: none;
    
    39
      list-style: none;
    
    40
      margin: 0;
    
    41
      padding: 10px 0;
    
    42
      border: 1px solid #cbd5e1;
    
    43
      border-radius: 6px;
    
    44
      box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
    
    45
      position: absolute;
    
    46
      top: 40px;
    
    47
      left: 5px;
    
    48
      min-width: 180px;
    
    49
      background: white;
    
    50
    }
    
    51
    52
    .dropdown-menu.active {
    
    53
      display: block;
    
    54
    }
    
    55
    56
    .dropdown-menu a {
    
    57
      display: block;
    
    58
    }
    

    Adicionando Interatividade com JavaScript

    Para ter certeza de que podemos reutilizar o componente suspenso várias vezes, escreverei o JavaScript de forma que considere quaisquer componentes suspensos em uma determinada página. Isso significa que tudo o que precisamos fazer é incluir o script para que ele funcione em muitos componentes suspensos, assumindo que o HTML e o CSS correspondam ao nosso trabalho anterior.

    1
    class Dropdown {
    
    2
    3
      constructor() {
    
    4
        this.dropdowns = document.querySelectorAll('.dropdown .dropdown-menu')
    
    5
        if (this. dropdowns.length) {
    
    6
          this.initialize();
    
    7
        }
    
    8
      }
    
    9
    10
    11
     initialize() {
    
    12
        document.addEventListener('click', (e) => {
    
    13
          if (e.target.classList.contains('dropdown-action')) {
    
    14
            this.hideOtherDropdowns(e.target);
    
    15
            this.handleClick(e.target);
    
    16
          } else {
    
    17
            this.hideOtherDropdowns(null);
    
    18
          }
    
    19
        });
    
    20
      }
    
    21
    22
      handleClick(dropdownAction) {
    
    23
        this. dropdowns.forEach(dropdown => {
    
    24
          if (dropdown.parentElement.contains(dropdownAction)) {
    
    25
            dropdown.classList.add('active');
    
    26
          } else {
    
    27
            dropdown.classList.remove('active');
    
    28
          }
    
    29
        })
    
    30
      }
    
    31
    32
      hideOtherDropdowns(dropdownAction) {
    
    33
    34
        this.dropdowns.forEach((dropdown) => {
    
    35
          if (!dropdown.parentElement.contains(dropdownAction)) {
    
    36
            dropdown.classList.remove('active');
    
    37
          }
    
    38
        });
    
    39
      }
    
    40
    41
    }
    
    42
    43
    document.addEventListener('DOMContentLoaded', () => new Dropdown);
    

    Usando a sintaxe moderna do ES6, criei um novo Dropdown class para encapsular a lógica para reutilização em uma determinada página. Inicializamos logo no início usando a seguinte linha.

    1
    document.addEventListener('DOMContentLoaded', () => new Dropdown);
    

    Desde que o script esteja incluído em uma determinada página, esse código é tudo o que você precisa para dar vida ao componente. Podemos esperar que o conteúdo do DOM seja carregado antes de inicializar o Dropdown classe para melhores resultados.

    Nossas funções (com mais detalhes)

    Vamos separar cada função e mergulhar mais fundo no que está acontecendo.

    O Método Construtor

    1
    constructor() {
    
    2
      this.dropdowns = document.querySelectorAll('.dropdown .dropdown-menu')
    
    3
      if (this. dropdowns.length) {
    
    4
        this.initialize();
    
    5
      }
    
    6
    }
    

    O constructor O método é usado para inicializar o objeto ou classe. Um construtor permite que você forneça qualquer inicialização personalizada antes que outros métodos possam ser chamados.

    Aqui eu adicionei um querySelectorAll método que consulta todos os menus suspensos em uma determinada página. Faremos um loop através deles nas próximas funções.

    Além disso, adicionei alguma lógica condicional para verificar se existem menus suspensos em uma página antes de chamar o initialize() função que faz parte da classe.

    A função inicializar()

    1
    initialize() {
    
    2
      document.addEventListener('click', (e) => {
    
    3
        if (e.target.classList.contains('dropdown-action')) {
    
    4
          this.hideOtherDropdowns(e.target);
    
    5
          this.handleClick(e.target);
    
    6
        } else {
    
    7
          this.hideOtherDropdowns(null);
    
    8
        }
    
    9
      });
    
    10
    }
    

    O initialize() função chama as outras funções dentro da classe. Aqui, adicionei um ouvinte de evento a todo o documento HTML. Precisamos fazer isso para responder à ação de um usuário quando ele clica fora de um determinado menu suspenso. Uma boa experiência é fechar automaticamente o menu ao clicar fora dele, então essa lógica é responsável por isso.

    Podemos tocar no “clique” usando o evento que é retornado e tocando nele para uso futuro. A ideia é usar o item em que o usuário clicou para abrir caminho para mostrar e ocultar os menus corretos na página. Se houver vários menus, queremos fechar automaticamente os menus que não estão em uso.

    Se o link do menu de navegação contiver o dropdown-action class, chamaremos duas outras funções e passaremos o mesmo valor de destino para elas. Caso contrário, fecharemos todos os outros menus suspensos usando o hideOtherDropdowns() função.

    A função handleClick()

    1
      handleClick(dropdownAction) {
    
    2
        this. dropdowns.forEach(dropdown => {
    
    3
          if (dropdown.parentElement.contains(dropdownAction)) {
    
    4
            dropdown.classList.add('active');
    
    5
          } else {
    
    6
            dropdown.classList.remove('active');
    
    7
          }
    
    8
        })
    
    9
      }
    

    O handleClick() função aceita o event.target propriedade. Passamos o imóvel por dentro do initialize() função. Vamos alavancar nosso anteriormente inicializado this.dropdowns array para percorrer todos os menus suspensos existentes. Se um menu suspenso contiver o event.target (ou dropdownAction), adicionamos a classe .active para o menu suspenso e mostre-o. Se não for o caso, removeremos a classe .active.

    O parâmetro dropdownAction

    1
    hideOtherDropdowns(dropdownAction) {
    
    2
      this. dropdowns.forEach((dropdown) => {
    
    3
        if (!dropdown.parentElement.contains(dropdownAction)) {
    
    4
          dropdown.classList.remove('active');
    
    5
        }
    
    6
      });
    
    7
    }
    

    Ter menos menus suspensos abertos ao mesmo tempo proporcionaria uma experiência melhor a longo prazo. Precisamos fechar qualquer um que não esteja ativamente em uso. Podemos descobrir qual é usado em relação ao event.target sendo passado do initialize() função. Aqui usamos um parâmetro chamado dropdownAction subscrever para o event.target propriedade.

    Faremos um loop por todos os menus suspensos mais uma vez. Dentro do forEach loop, podemos executar uma instrução condicional que verifica se o menu suspenso contém o dropdownAction ou não. vamos usar o ! para indicar que verificaremos o inverso da lógica. Portanto, em termos simples, estou verificando se o menu suspenso não contém o link para abrir o menu suspenso. Caso contrário, removeremos sua classe ativa e a ocultaremos.

    Conclusão

    Com alguma sorte, agora você pode tentar abrir e fechar os dois menus suspensos que adicionamos no HTML, e eles devem responder como quisermos. Quando um abre, o outro fecha. Observe também que quando você clica fora de um menu suspenso, o menu suspenso original também fecha.

    Espero que tenha gostado de seguir este tutorial de JavaScript e aprendido algo que possa usar. Obrigado por ler!

    Deixe uma resposta