Come realizzare un effetto hover sottolineato in movimento con CSS e Javascript
() translation by (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
active
al genitore immediato (li
) del link in questione. - Diminuiamo l'
opacity
da tutti i link del menu, tranne da quello "attivo". - Usiamo il metodo
getBoundingClientRect
per 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-color
dell'elementospan
. Ricordate, il valore della sua proprietà iniziale è impostato sutransparent
. - Assegniamo i valori estratti al metodo
getBoundingClientRect
alla corrispondente proprietà dell'elementospan
. In altre parole, il tagspan
eredita 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
left
etop
della 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.