Um dos meus primeiros tutoriais aqui no Tuts+ em 2015 abordou a criação de um menu deslizante fora da tela com a versão jQuery de mmenu.js. Se você verificar o projeto de demonstração, notará que o menu inclui vários níveis.
Preparados para mais um desafio?!
1. Definir a marcação HTML
A marcação para nosso menu móvel consistirá nos seguintes elementos:
- UMA
header
com umnav
dentro dele. - UMA
main
onde ficará o conteúdo principal da nossa página.
Dentro de nav
vamos colocar dois div
s. O primeiro terá o header-bar
classe, enquanto a segunda terá a menu-wrapper
1.
o .header-bar
será composto por três elementos:
- O menu de alternância
- O logotipo da empresa
- A conta do Twitter da empresa
Dentro de .menu-wrapper
vamos colocar três div
s com a classe de list-wrapper
. vamos chamá-los painéis Pela simplicidade. Pontos a serem observados:
- No primeiro painel, especificaremos a estrutura do menu com itens de menu pai e filho. Para fazer isso, usaremos uma marcação típica com listas não ordenadas aninhadas.
- O segundo e o terceiro painéis conterão um de volta botão e um vazio
div
com a classe desub-menu-wrapper
. Mais sobre o trabalho deles:- O botão Voltar nos ajudará a subir um nível. Dito isto, do nível três para o nível dois e do nível dois para o nível um.
- o
.sub-menu-wrapper
do segundo painel conterá os links de segundo nível. Da mesma forma, o.sub-menu-wrapper
do terceiro painel conterá os links de terceiro nível. Vamos inserir esses links dinamicamente através do JavaScript.
Com tudo isso em mente, surge a seguinte marcação:
...
2. Especifique os estilos principais
Vamos agora nos concentrar nos estilos de cabeçalho para nosso menu móvel.
Para simplificar, não examinarei todos os estilos, mas sinta-se à vontade para examiná-los clicando na guia CSS do projeto de demonstração.
Algumas coisas a serem observadas:
- O cabeçalho será um elemento de posição fixa e terá largura máxima de 600px.
- o
.header-bar
terá uma altura fixa de 60px. - o
.menu-wrapper
estará absolutamente posicionado e ficará embaixo do.header-bar
. Além disso, terá uma altura igual à altura da viewport menos a.header-bar
altura de. Por fim, inicialmente ficará oculto.
Os estilos associados:
/*CUSTOM VARIABLES HERE*/ .page-header { position: fixed; top: 0; left: 50%; transform: translateX(-50%); width: 100%; max-width: 600px; margin: 0 auto; color: var(--white); } .page-header .header-bar { display: flex; justify-content: space-between; align-items: center; height: 60px; padding: 0 20px; background: var(--header-bar-bg); } .page-header .menu-wrapper { display: none; position: absolute; top: 60px; left: 0; width: 100%; height: calc(100vh - 60px); overflow: hidden; }
Vamos continuar com os estilos de painel.
- Todos eles receberão a altura de seus pais e terão
overflow-y: auto
. Essa propriedade garante que uma barra de rolagem apareça caso haja muitos links de menu dentro dela. - Especialmente o segundo e o terceiro painéis estarão absolutamente posicionados e fora da tela por padrão.
Os estilos associados:
/*CUSTOM VARIABLES HERE*/ .page-header .list-wrapper { height: 100%; padding: 30px 20px; overflow-y: auto; background: var(--menu-bg); } .page-header .list-wrapper:nth-child(2), .page-header .list-wrapper:nth-child(3) { position: absolute; top: 0; left: 0; right: 0; transform: translateX(100%); backface-visibility: hidden; transition: transform 0.5s; }
Em seguida, ocultaremos todos os menus aninhados do primeiro e terceiro painéis:
.page-header .list-wrapper:nth-child(1) > ul > li > .sub-menu, .page-header .list-wrapper:nth-child(2) .level-3 { display: none; }
Continuando, definiremos alguns estilos para os links do menu móvel, especificamente:
- Os links que abrem um menu aninhado serão sublinhados.
- Para indicar que um link está ativo ou sobrevoado, daremos a ele uma cor diferente e um caractere.
Os estilos associados:
/*CUSTOM VARIABLES HERE*/ .page-header .menu-wrapper a { display: inline-block; position: relative; padding: 5px 0; } .page-header .menu-wrapper a.nested { text-decoration: underline; } .page-header .menu-wrapper a:hover, .page-header .menu-wrapper a.is-active { color: var(--orange); } .page-header .menu-wrapper a:hover::before, .page-header .menu-wrapper a.is-active::before { content: "✦"; position: absolute; top: 50%; right: -20px; transform: translateY(-50%); color: var(--orange); } .page-header .back-one-level { display: flex; align-items: center; margin-bottom: 40px; }
Por fim, especificaremos alguns estilos simples para o botão Voltar.
Aqui estão eles:
/*CUSTOM VARIABLES HERE*/ .page-header .back-one-level { display: flex; align-items: center; margin-bottom: 40px; } .page-header .back-one-level svg { fill: var(--white); margin-right: 10px; }
3. Adicione o JavaScript
Depois de configurar os estilos, é hora de discutir as ações necessárias para revelar os níveis de menu aninhados com animação de slides.
Alternar menu
Aqui está um GIF animado que ilustra o estado de alternância do menu:
Cada vez que clicarmos no botão de alternância, realizaremos as seguintes ações:
- Alternar a visibilidade do menu através do
is-visible
classe. Se estiver oculto, aparecerá e vice-versa. - Verifique se o menu está fechado. Se essa condição for atendida, faremos o seguinte:
- Remova o
is-visible
classe do segundo e terceiro painéis, se o tiverem. - Remova o
is-active
class nos links do menu ativo, se houver tais elementos.
- Remova o
Aqui está o código JavaScript necessário:
const pageHeader = document.querySelector(".page-header"); const toggleMenu = pageHeader.querySelector(".toggle-menu"); const menuWrapper = pageHeader.querySelector(".menu-wrapper"); const listWrapper2 = pageHeader.querySelector(".list-wrapper:nth-child(2)"); const listWrapper3 = pageHeader.querySelector(".list-wrapper:nth-child(3)"); const isVisibleClass = "is-visible"; const isActiveClass = "is-active"; toggleMenu.addEventListener("click", function () { // 1 menuWrapper.classList.toggle(isVisibleClass); // 2 if (!this.classList.contains(isVisibleClass)) { // 1 listWrapper2.classList.remove(isVisibleClass); listWrapper3.classList.remove(isVisibleClass); // 2 const menuLinks = menuWrapper.querySelectorAll(".is-active"); for (const menuLink of menuLinks) { menuLink.classList.remove(isActiveClass); } } });
E os estilos relevantes:
.page-header .menu-wrapper.is-visible { display: block; } .page-header .list-wrapper:nth-child(2), .page-header .list-wrapper:nth-child(3) { transition: transform 0.5s; } .page-header .list-wrapper:nth-child(2).is-visible, .page-header .list-wrapper:nth-child(3).is-visible { transform: none; }
Abrir Nível Dois
Aqui está um GIF animado que ilustra como os menus de segundo nível aparecerão:
Cada vez que clicarmos em um link de menu do primeiro nível (os visíveis), verificaremos se ele possui um menu aninhado como irmão. Se essa condição for atendida, realizaremos as seguintes ações:
- Impedir sua ação padrão.
- Atribuir o
is-active
aula para isso. - Crie uma cópia profunda de seu irmão.
- Anexe este nó recém-criado ao
.sub-menu-wrapper
do segundo painel. - Revele o segundo painel com animação de slides.
Aqui está o código JavaScript necessário:
... for (const level1Link of level1Links) { level1Link.addEventListener("click", function (e) { const siblingList = level1Link.nextElementSibling; if (siblingList) { // 1 e.preventDefault(); // 2 this.classList.add(isActiveClass); // 3 const cloneSiblingList = siblingList.cloneNode(true); // 4 subMenuWrapper2.innerHTML = ""; subMenuWrapper2.append(cloneSiblingList); // 5 listWrapper2.classList.add(isVisibleClass); } }); }
E os estilos associados para a animação:
.page-header .list-wrapper:nth-child(2) { transition: transform 0.5s; } .page-header .list-wrapper:nth-child(2).is-visible { transform: none; }
Abrir Nível Três
Aqui está um GIF animado que ilustra como os menus de terceiro nível aparecerão:
Lembre-se que, por padrão, não há nenhum conteúdo dentro do .sub-menu-wrapper
do segundo painel. Na seção anterior, descrevemos como ele recebe o conteúdo realizando uma cópia profunda.
Aqui está um exemplo da marcação gerada:
Em seguida, temos que realizar algumas ações assim que alguém clicar em um link de menu do segundo nível. No entanto, o complicado é que esses links são gerados dinamicamente e não fazem parte do DOM inicial. Dito isso, o click
evento não funcionará para esses elementos 🙁
Felizmente, a solução é bastante simples. graças ao delegação do eventovamos anexar o click
evento para o painel pai que faz parte do DOM. Em seguida, através do target
propriedade desse evento, verificaremos os elementos nos quais o evento ocorreu para garantir que sejam links com um irmão de submenu.
Assumindo que esses são os elementos de destino, para cada um deles, faremos o seguinte (semelhante à seção anterior):
- Impedir sua ação padrão.
- Atribuir o
is-active
aula para isso. - Crie uma cópia (profunda) de seu irmão. Observe que não haverá nenhuma alteração se fizermos uma cópia profunda ou não, pois nosso menu contém três níveis.
- Anexe este nó recém-criado ao
.sub-menu-wrapper
do terceiro painel. - Revele o terceiro painel com animação de slides.
Aqui está o código JavaScript necessário:
... listWrapper2.addEventListener("click", function (e) { const target = e.target; if (target.tagName.toLowerCase() === "a" && target.nextElementSibling) { const siblingList = target.nextElementSibling; // 1 e.preventDefault(); // 2 target.classList.add(isActiveClass); // 3 const cloneSiblingList = siblingList.cloneNode(true); // 4 subMenuWrapper3.innerHTML = ""; subMenuWrapper3.append(cloneSiblingList); // 5 listWrapper3.classList.add(isVisibleClass); } });
E os estilos relevantes para a animação:
.page-header .list-wrapper:nth-child(3) { transition: transform 0.5s; } .page-header .list-wrapper:nth-child(3).is-visible { transform: none; }
Voltar um nível
Aqui está um GIF animado que ilustra como os botões Voltar funcionarão:
Cada vez que clicarmos em um botão Voltar, realizaremos as seguintes ações:
- Encontre o painel pai do botão Voltar de destino e remova seu
is-visible
classe. - Remova o
is-active
class do link ativo do irmão anterior do painel pai.
Aqui está o código JavaScript necessário:
... for (const backOneLevelBtn of backOneLevelBtns) { backOneLevelBtn.addEventListener("click", function () { // 1 const parent = this.closest(".list-wrapper"); parent.classList.remove(isVisibleClass); // 2 parent.previousElementSibling .querySelector(".is-active") .classList.remove(isActiveClass); }); }
E, novamente, existem alguns estilos CSS que acompanham essa ação.
EXTRA: Atualizar o texto anterior
Este tutorial foi atualizado com uma etapa extra, solicitada por um de nossos leitores (ótima ideia!)
Agora que nosso menu móvel funciona da maneira que queremos, podemos criar diferentes variações dele. Por exemplo, vamos supor que queremos substituir o código fixo De volta text com o texto do link do menu que está sendo clicado.
Para manter a consistência, manteremos nosso HTML como está e adicionaremos apenas um pouco de JavaScript (marcado com o EXTRA comente) assim:
... // EXTRA const backLabel2 = listWrapper2.querySelector(".back-one-level span"); const backLabel3 = listWrapper3.querySelector(".back-one-level span"); toggleMenu.addEventListener("click", function () { menuWrapper.classList.toggle(isVisibleClass); if (!this.classList.contains(isVisibleClass)) { // EXTRA OPTIONALLY backLabel2.textContent = ""; backLabel3.textContent = ""; } }); for (const level1Link of level1Links) { level1Link.addEventListener("click", function (e) { const siblingList = level1Link.nextElementSibling; if (siblingList) { // EXTRA backLabel2.textContent = level1Link.textContent; } }); } listWrapper2.addEventListener("click", function (e) { const target = e.target; if (target.tagName.toLowerCase() === "a" && target.nextElementSibling) { ... // EXTRA backLabel3.textContent = target.textContent; } }); ...
Conclusão
Neste tutorial, criamos um menu móvel de vários níveis aproveitando alguns estilos CSS comuns e a API do JavaScript DOM. Mais importante, nossa marcação inicial contém listas aninhadas simples para criação de menu. Isso significa que podemos transformá-lo em dinâmico e aproveitar os recursos de um CMS como o WordPress com apenas algumas modificações.
Tenho certeza de que haverá outras soluções para a construção desses menus, talvez mais eficazes ou acessíveis do que esta. Nossa abordagem tira grande proveito da cloneNode()
método que cria nós duplicados, portanto, deve ser usado com cautela, especialmente se nossos submenus contiverem IDs.
Aqui está um lembrete do nosso exercício de hoje (este é o dinâmico exemplo de texto de volta):
Se você gostou deste exercício e economizou algum tempo para construir uma solução do zero, não se esqueça de dar um pouco de ❤️. Além disso, se você quiser ver outra versão ou extensão deste menu, nos informe!
Como sempre, muito obrigado pela leitura!