No tutorial de hoje, vamos usar um pouco de CSS e JavaScript para criar um efeito de menu flutuante sofisticado. Não é um resultado final complicado, mas construí-lo será uma ótima oportunidade para praticar nossas habilidades de front-end.
Sem mais introduções, vamos conferir o que vamos construir:
A marcação
Começamos com algumas marcações bem básicas; uma nav
elemento que contém o menu e um vazio span
elemento:
O CSS
Com a marcação pronta, a seguir especificamos alguns estilos básicos para os elementos relacionados:
.mynav ul { display: flex; justify-content: center; flex-wrap: wrap; list-style-type: none; padding: 0; } .mynav li:not(:last-child) { margin-right: 20px; } .mynav a { display: block; font-size: 20px; color: black; text-decoration: none; padding: 7px 15px; } .target { position: absolute; border-bottom: 4px solid transparent; z-index: -1; transform: translateX(-60px); } .mynav a, .target { transition: all .35s ease-in-out; }
Observe que o span
elemento (.target
) está absolutamente posicionado. Como veremos em breve, usaremos JavaScript para determinar sua posição exata. Além disso, deve aparecer atras do os links do menu, por isso damos uma nota negativa z-index
.
-
A propriedade z-index em CSS parece bastante simples, mas há muito o que descobrir sob a superfície se você realmente quiser entender como ela funciona. Nisso…
O JavaScript
Neste ponto, vamos focar nossa atenção no JavaScript necessário. Para começar, temos como alvo os elementos desejados. Também definimos um array de cores que usaremos mais tarde.
const target = document.querySelector(".target"); const links = document.querySelectorAll(".mynav a"); const colors = ["deepskyblue", "orange", "firebrick", "gold", "magenta", "black", "darkblue"];
Eventos
A seguir, ouvimos o click
e mouseenter
eventos dos links do menu.
Quando o click
evento acontecer, impedimos que a página seja recarregada. Claro, isso funciona no nosso caso porque todos os links têm um vazio href
atributo. Em um projeto real, no entanto, cada um dos links do menu provavelmente abriria uma página diferente.
O mais importante é que assim que o mouseenter
incêndios de eventos, o mouseenterFunc
função de retorno de chamada é executada:
for (let i = 0; i < links.length; i++) { links[i].addEventListener("click", (e) => e.preventDefault()); links[i].addEventListener("mouseenter", mouseenterFunc); }
mouseenterFunc
O corpo do mouseenterFunc
função fica assim:
function mouseenterFunc() { for (let i = 0; i < links.length; i++) { if (links[i].parentNode.classList.contains("active")) { links[i].parentNode.classList.remove("active"); } links[i].style.opacity = "0.25"; } this.parentNode.classList.add("active"); this.style.opacity = "1"; const width = this.getBoundingClientRect().width; const height = this.getBoundingClientRect().height; const left = this.getBoundingClientRect().left; const top = this.getBoundingClientRect().top; const color = colors[Math.floor(Math.random() * colors.length)]; target.style.width = `${width}px`; target.style.height = `${height}px`; target.style.left = `${left}px`; target.style.top = `${top}px`; target.style.borderColor = color; target.style.transform = "none"; }
Dentro desta função fazemos o seguinte:
- Adicione o
active
class para o pai imediato (li
) do link de destino. - Diminua o
opacity
de todos os links do menu, exceto o "ativo". - Use o
getBoundingClientRect
método para recuperar o tamanho do link associado e sua posição em relação à viewport. - Obtenha uma cor aleatória da matriz mencionada e passe-a como valor para o
border-color
propriedade dospan
elemento. Lembre-se, seu valor de propriedade inicial é definido comotransparent
. - Atribua os valores extraídos do
getBoundingClientRect
método para as propriedades correspondentes dospan
elemento. Em outras palavras, ospan
tag herda o tamanho e a posição do link sobre o qual o cursor está passando. - Redefina a transformação padrão aplicada ao
span
elemento. Esse comportamento só é importante na primeira vez que passamos o mouse sobre um link. Neste caso, a transformação do elemento vai detransform: translateX(-60px)
paratransform: none
. Isso nos dá um bom efeito de deslizamento.
Se ativo
É importante observar que o código acima é executado toda vez que passamos o mouse sobre um link. Portanto, ele também é executado quando passamos o mouse sobre um link “ativo”. Para evitar esse comportamento, envolvemos o código acima dentro de um if
declaração:
function mouseenterFunc() { if (!this.parentNode.classList.contains("active")) { // code here } }
Até agora, nossa demonstração é a seguinte:
Quase, mas não completamente
Então, tudo parece funcionar como esperado, certo? Bem, isso não é verdade porque se rolarmos pela página ou redimensionarmos a janela de visualização e tentarmos selecionar um link, as coisas ficarão confusas. Especificamente, a posição do span
elemento fica incorreto.
Brinque com a demonstração de página inteira (certifique-se de ter adicionado conteúdo fictício suficiente) para ver o que quero dizer.
Para resolvê-lo, temos que calcular o quanto rolamos do topo da janela e adicionar esse valor ao valor atual top
valor do elemento de destino. Da mesma forma, devemos calcular o quanto o documento foi rolado horizontalmente (apenas no caso). O valor resultante é adicionado ao valor atual left
valor do elemento de destino.
Aqui estão as duas linhas de código que atualizamos:
const left = this.getBoundingClientRect().left + window.pageXOffset; const top = this.getBoundingClientRect().top + window.pageYOffset;
Tenha em mente que todo o código acima é executado assim que o navegador processa o DOM e encontra o script relevante. Novamente, para suas próprias implementações e designs, você pode querer executar este código quando a página for carregada, ou algo assim. Nesse cenário, você terá que incorporá-lo em um manipulador de eventos (por exemplo, load
manipulador de eventos).
Janela de exibição
A última coisa que precisamos fazer é garantir que o efeito ainda funcione conforme redimensionamos a janela do navegador. Para isso, ouvimos o resize
evento e registre o resizeFunc
manipulador de eventos.
window.addEventListener("resize", resizeFunc);
Aqui está o corpo deste manipulador:
function resizeFunc() { const active = document.querySelector(".mynav li.active"); if (active) { const left = active.getBoundingClientRect().left + window.pageXOffset; const top = active.getBoundingClientRect().top + window.pageYOffset; target.style.left = `${left}px`; target.style.top = `${top}px`; } }
Dentro da função acima, fazemos o seguinte:
- Verifique se há um item de lista de menu com a classe de
active
. Se lá é tal elemento, que indica que já passamos o mouse sobre um link. - Obtenha o novo
left
etop
propriedades do item “ativo” junto com as propriedades de janela relacionadas e atribua-as aospan
elemento. Observe que recuperamos os valores apenas para as propriedades que mudam durante oresize
evento. Isso significa que não há necessidade de recalcular a largura e a altura dos links do menu.
Suporte ao navegador
A demonstração funciona bem em todos os navegadores recentes. Se você encontrar algum problema, deixe-me saber nos comentários abaixo. Além disso, como você deve ter notado, usamos Babel para compilar nosso código ES6 para ES5.
Conclusão
Neste tutorial, passamos pelo processo de criação de um efeito de foco de menu simples, mas interessante.
Espero que você tenha gostado do que construímos aqui e se inspirado para desenvolver efeitos de menu ainda mais poderosos, como o que aparece (no momento da redação) no site do Stripe.
Você já criou algo parecido? Se sim, não deixe de compartilhar conosco os desafios que você enfrentou.
Originally posted 2022-06-22 19:55:52.