Come realizzare un effetto hover sottolineato in movimento con CSS e Javascript
Italian (Italiano) translation by Cinzia Sgariglia (you can also view the original English article)
Nel tutorial di oggi, andremo a usare un po' di CSS e Javascript per creare un menu con effetto hover fantasioso. Non è complicato come risultato finale, eppure realizzarlo sarà una grande opportunità di praticare le nostre abilità come front-end.
Senza ulteriori indugi, diamo un'occhiata a cosa realizzeremo:
Il markup
Iniziamo con del markup molto di base; un elemento nav che contiene il menu e un elemento span vuoto:
1 |
<nav class="mynav"> |
2 |
<ul>
|
3 |
<li>
|
4 |
<a href="">Home</a> |
5 |
</li>
|
6 |
<li>
|
7 |
<a href="">About</a> |
8 |
</li>
|
9 |
<li>
|
10 |
<a href="">Company</a> |
11 |
</li>
|
12 |
<li>
|
13 |
<a href="">Work</a> |
14 |
</li>
|
15 |
<li>
|
16 |
<a href="">Clients</a> |
17 |
</li>
|
18 |
<li>
|
19 |
<a href="">Contact</a> |
20 |
</li>
|
21 |
</ul>
|
22 |
</nav>
|
23 |
|
24 |
<span class="target"></span> |
Il CSS
Con il markup pronto, dopo specifichiamo alcuni stili di base per gli elementi collegati:
1 |
.mynav ul { |
2 |
display: flex; |
3 |
justify-content: center; |
4 |
flex-wrap: wrap; |
5 |
list-style-type: none; |
6 |
padding: 0; |
7 |
}
|
8 |
|
9 |
.mynav li:not(:last-child) { |
10 |
margin-right: 20px; |
11 |
}
|
12 |
|
13 |
.mynav a { |
14 |
display: block; |
15 |
font-size: 20px; |
16 |
color: black; |
17 |
text-decoration: none; |
18 |
padding: 7px 15px; |
19 |
}
|
20 |
|
21 |
.target { |
22 |
position: absolute; |
23 |
border-bottom: 4px solid transparent; |
24 |
z-index: -1; |
25 |
transform: translateX(-60px); |
26 |
}
|
27 |
|
28 |
.mynav a, |
29 |
.target { |
30 |
transition: all .35s ease-in-out; |
31 |
}
|
Notiamo che l'elemento span (.target) è posizionato in modo absolute. Come vedremo in un istante, useremo Javascript per determinare la sua esatta posizione. In aggiunta, dovrebbe apparire dietro i link del menu, così gli diamo uno z-index negativo.
Javascript
A questo punto, focalizziamo la nostra attenzione sul Javascript necessario. Per iniziare, ci dirigiamo sugli elementi desiderati. Definiamo anche un array di colori che useremo più tardi.
1 |
const target = document.querySelector(".target"); |
2 |
const links = document.querySelectorAll(".mynav a"); |
3 |
const colors = ["deepskyblue", "orange", "firebrick", "gold", "magenta", "black", "darkblue"]; |
Eventi
Dopo restiamo in ascolto degli eventi click e mouseenter dei link del menu.
Quando l'evento click si verifica, impediamo alla pagina di ricaricarsi. naturalmente, nel nostro caso funziona perché tutti i link hanno un attributo href vuoto. In un progetto vero tuttavia, ognuno dei link del menu probabilmente aprirà una pagina diversa.
Più importante, non appena l'evento mouseenter parte, la funzione di callback mouseenterFunc è eseguita:
1 |
for (let i = 0; i < links.length; i++) { |
2 |
links[i].addEventListener("click", (e) => e.preventDefault()); |
3 |
links[i].addEventListener("mouseenter", mouseenterFunc); |
4 |
}
|
mouseenterFunc
Il corpo della funzione mouseenterFunc somiglia a questo:
1 |
function mouseenterFunc() { |
2 |
for (let i = 0; i < links.length; i++) { |
3 |
if (links[i].parentNode.classList.contains("active")) { |
4 |
links[i].parentNode.classList.remove("active"); |
5 |
}
|
6 |
links[i].style.opacity = "0.25"; |
7 |
}
|
8 |
|
9 |
this.parentNode.classList.add("active"); |
10 |
this.style.opacity = "1"; |
11 |
|
12 |
const width = this.getBoundingClientRect().width; |
13 |
const height = this.getBoundingClientRect().height; |
14 |
const left = this.getBoundingClientRect().left; |
15 |
const top = this.getBoundingClientRect().top; |
16 |
const color = colors[Math.floor(Math.random() * colors.length)]; |
17 |
|
18 |
target.style.width = `${width}px`; |
19 |
target.style.height = `${height}px`; |
20 |
target.style.left = `${left}px`; |
21 |
target.style.top = `${top}px`; |
22 |
target.style.borderColor = color; |
23 |
target.style.transform = "none"; |
24 |
}
|
All'interno di questa funzione facciamo i seguenti passi:
- Aggiungiamo la classe
activeal genitore immediato (li) del link in questione. - Diminuiamo l'
opacityda tutti i link del menu, tranne da quello "attivo". - Usiamo il metodo
getBoundingClientRectper recuperare la misura del link associato e la sua posizione relativa al viewport. - Prendiamo un colore a caso dall'array menzionato prima e lo passiamo come valore alla proprietà
border-colordell'elementospan. Ricordate, il valore della sua proprietà iniziale è impostato sutransparent. - Assegniamo i valori estratti al metodo
getBoundingClientRectalla corrispondente proprietà dell'elementospan. In altre parole, il tagspaneredita la misura e la posizione del link su cui abbiamo portato il mouse. - Resettiamo la trasformazione di predefinita applicata all'elemento
span. Il comportamento è importante solo la prima volta che portiamo il mouse su un link. In questo caso, la trasformazione dell'elemento va datransform: translateX(-60px)atransform: none. Ciò gli dà un piacevole effetto scorrimento verso l'interno.
Se attivo
È importante notare che il codice sopra è eseguito ogni volta che portiamo il mouse su un link. Di conseguenza gira anche quando portiamo il mouse su un link "attivo". Per prevenire questo comportamento, avvolgiamo il codice in una dichiarazione if:
1 |
function mouseenterFunc() { |
2 |
if (!this.parentNode.classList.contains("active")) { |
3 |
// code here
|
4 |
}
|
5 |
}
|
Finora, la nostra demo appare come segue:
Quasi, ma non del tutto
Quindi, ogni cosa sembra funzionare come ci aspettavamo, giusto? Beh, non è vero perché se scorriamo attraverso la pagina o ridimensioniamo il viewport e poi cerchiamo di selezionare un link, le cose diventano caotiche. Precisamente, la posizione dell'elemento span diventa scorretta.
Giocherellate con la demo a piena pagina per vedere cosa intendo.
Per risolverlo, dobbiamo calcolare quanta distanza abbiamo scorso dall'inizio della finestra e aggiungere questo valore la valore top attuale dell'elemento di riferimento. Allo stesso modo dovremmo calcolare di quanto il documento è stato scorso orizzontalmente (per ogni eventualità). Il valore risultante viene aggiunto la valore left attuale dell'elemento di riferimento.
Ecco le due linee di codice che abbiamo aggiornato:
1 |
const left = this.getBoundingClientRect().left + window.pageXOffset; |
2 |
const top = this.getBoundingClientRect().top + window.pageYOffset; |
Tenete a mente che tutto il codice sopra è eseguito non appena il browser esamina il DOM e trova lo script attinente. Ancora, per le vostre proprie implementazioni e progetti potreste voler lanciare questo codice quando si carica la pagina, o qualcosa del genere. In un tale scenario, dovrete inserirlo in un gestore di eventi (es. gestore di eventi load).
Viewport
L'ultima cosa da fare è assicurarsi che l'effetto funzionerà ancora quando ridimensioneremo la finestra del browser. Per raggiungere ciò, restiamo in ascolto dell'evento resize e registriamo il gestore eventi resizeFunc.
1 |
window.addEventListener("resize", resizeFunc); |
Ecco il corpo di questo gestore:
1 |
function resizeFunc() { |
2 |
const active = document.querySelector(".mynav li.active"); |
3 |
|
4 |
if (active) { |
5 |
const left = active.getBoundingClientRect().left + window.pageXOffset; |
6 |
const top = active.getBoundingClientRect().top + window.pageYOffset; |
7 |
|
8 |
target.style.left = `${left}px`; |
9 |
target.style.top = `${top}px`; |
10 |
}
|
11 |
}
|
All'interno della funzione sopra, facciamo le seguenti cose:
- Controlliamo se c'è una voce nella lista del menu con la classe
active. Se c'è tale elemento, ciò significa che abbiamo già portato il mouse su un link. - Prendete le nuove proprietà di
leftetopdella voce "attiva" insieme alle proprietà della finestra connessa e assegnategli l'elementospan. Notate che recuperiamo i valori solo per le proprietà che cambiano durante l'eventoresize. Ciò significa che non c'è bisogno di ricalcolare la larghezza e l'altezza dei link del menu.
Supporto browser
La demo funziona bene in tutti i browser recenti. Se però vi imbattete in un qualche problema, fatemelo sapere nei commenti sotto. Inoltre, come avete probabilmente notato, abbiamo usato Babel per compilare il nostro codice ES6 fino a ES5.
Conclusioni
In questa veloce dritta siamo andati oltre il processo di creazione di un semplice, eppure interessante effetto hover su menu.
Spero che vi sia piaciuto ciò che abbiamo realizzato qui e vi abbia dato un'ispirazione per sviluppare effetti menu ancora più potenti come quello che appare (al momento in cui scrivo) sul sito di Stripe.
Avete mai creato qualcosa di simile? Se si, assicuratevi di condividere con noi le sfide che avete affrontato.



