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 |
|
2 |
href="#">Home
|
3 |
|
4 |
href="#" class="dropdown-action">
|
5 |
Services |
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
href="#">About
|
16 |
href="#">Contact
|
17 |
|
18 |
href="#" class="dropdown-action">
|
19 |
Account |
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
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!