Comment construire un changement d'effet de relance de soulignement avec CSS et JavaScript
() translation by (you can also view the original English article)
Dans le tutoriel d'aujourd'hui, nous allons utiliser un peu de CSS et JavaScript pour créer un effet fantaisie de survol du menu. Ce n'est pas un résultat final compliqué, mais la construction de celui-ci sera une excellente occasion de pratiquer nos compétences de front-end.
Sans autre intro, voyons ce que nous allons construire:
Le balisage
Nous commençons par un balisage très élémentaire; un élément nav
qui contient le menu et un élément span
vide:
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> |
Le CSS
Avec le balisage prêt, nous allons ensuite spécifier quelques styles de base pour les éléments associés:
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 |
}
|
Notez que l'élément span
(.target
) est absolument positionné. Comme nous le verrons dans un instant, nous utiliserons JavaScript pour déterminer sa position exacte. En outre, il devrait apparaître derrière les liens de menu, donc nous lui donnons un z-index
négatif.
Le JavaScript
À ce stade, concentrons notre attention sur le JavaScript requis. Pour commencer, nous ciblons les éléments souhaités. Nous définissons également un tableau de couleurs que nous utiliserons plus tard.
1 |
const target = document.querySelector(".target"); |
2 |
const links = document.querySelectorAll(".mynav a"); |
3 |
const colors = ["deepskyblue", "orange", "firebrick", "gold", "magenta", "black", "darkblue"]; |
Événements
Ensuite, nous écoutons les événements click
et mouseenter
des liens du menu.
Lorsque l'événement click
se produit, nous empêchons la page de recharger. Bien sûr, cela fonctionne dans notre cas car tous les liens ont un attribut href
vide. Dans un projet réel cependant, chacun des liens de menu ouvrirait probablement une page différente.
Plus important encore, dès que l'événement mouseenter
se déclenche, la fonction callback mouseenterFunc
est exécutée:
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
Le corps de la fonction mouseenterFunc
ressemble à ceci:
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 |
}
|
Dans cette fonction nous faisons ce qui suit:
- Ajoutez la classe
active
au parent immédiat (li
) du lien cible. - Diminuez l'
opacity
de tous les liens de menu, en dehors de l'"actif ". - Utilisez la méthode
getBoundingClientRect
pour récupérer la taille du lien associé et sa position par rapport à la fenêtre. - Obtenez une couleur aléatoire de la matrice mentionnée ci-dessus et transmettez-la en tant que valeur à la propriété
border-color
de l'élémentspan
. N'oubliez pas que sa valeur de propriété initiale est définie surtransparent
. - Attribuez les valeurs extraites de la méthode
getBoundingClientRect
aux propriétés correspondantes de l'élémentspan
. En d'autres termes, la balisespan
hérite de la taille et de la position du lien sur lequel on traverse. - Réinitialiser la transformation par défaut appliquée à l'élément
span
. Ce comportement n'est important que la première fois que nous survolons un lien. Dans ce cas, la transformation de l'élément passe detransform: translateX(-60px)
àtransform: none
. Cela nous donne un bel effet de glissement.
If Active
Il est important de noter que le code ci-dessus est exécuté chaque fois que nous survolons un lien. Il fonctionne donc lorsque nous planer sur un lien "actif" ainsi. Pour éviter ce comportement, nous enveloppons le code ci-dessus dans une instruction if
:
1 |
function mouseenterFunc() { |
2 |
if (!this.parentNode.classList.contains("active")) { |
3 |
// code here
|
4 |
}
|
5 |
}
|
Jusqu'à présent, notre démo se présente comme suit:
Presque, mais pas assez
Donc, tout semble fonctionner comme prévu, non? Eh bien, ce n'est pas vrai, car si nous faisons défiler la page ou redimensionnons la fenêtre, puis essayons de sélectionner un lien, les choses deviennent désordonnées. Plus précisément, la position de l'élément de span
devient incorrecte.
Jouez avec la démo de page pleine pour voir ce que je veux dire.
Pour le résoudre, nous devons calculer la distance parcourue depuis le haut de la fenêtre et ajouter cette valeur à la valeur top
courante de l'élément cible. De la même façon, nous devons calculer jusqu'à quel point le document a été défilé horizontalement (juste au cas où). La valeur résultante est ajoutée à la valeur de left
actuelle de l'élément cible.
Voici les deux lignes de code que nous mettons à jour:
1 |
const left = this.getBoundingClientRect().left + window.pageXOffset; |
2 |
const top = this.getBoundingClientRect().top + window.pageYOffset; |
Gardez à l'esprit que tout le code ci-dessus est exécuté dès que le navigateur traite le DOM et trouve le script approprié. Encore une fois, pour vos propres implémentations et conceptions, vous voudrez peut-être exécuter ce code lorsque la page est chargée, ou quelque chose comme ça. Dans un tel scénario, vous devrez l'intégrer dans un gestionnaire d'événements (ex. load
event handler).
Point de vue
La dernière chose que nous devons faire est de s'assurer que l'effet continuera à fonctionner comme nous redimensionner la fenêtre du navigateur. Pour le faire, nous écoutons l'événement de resize
et inscrivons le gestionnaire d'événements resizeFunc
.
1 |
window.addEventListener("resize", resizeFunc); |
Voici le corps de ce gestionnaire:
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 |
}
|
Dans la fonction ci-dessus, nous procédons comme suit:
- Vérifiez s'il y a un élément de liste de menu avec la classe
active
. S'il existe un tel élément, cela indique que nous avons déjà survolé un lien. - Obtenez les nouvelles propriétés de
left
et detop
de l'élément "active" ainsi que les propriétés de fenêtre associées et attribuez-les à l'élémentspan
. Notez que nous récupérons les valeurs uniquement pour les propriétés qui changent pendant l'événement deresize
. Cela signifie qu'il n'est pas nécessaire de recalculer la largeur et la hauteur des liens de menu.
Prise en charge du navigateur
La démo fonctionne bien dans tous les navigateurs récents. Si vous rencontrez des problèmes cependant, laissez-moi savoir dans les commentaires ci-dessous. En outre, comme vous l'avez peut-être remarqué, nous utilisons Babel pour compiler notre code ES6 jusqu'à ES5.
Conclusion
Dans cette astuce rapide nous avons traversé le processus de création d'un simple, mais intéressant menu hover effet.
J'espère que vous avez apprécié ce que nous avons construit ici et s'est inspiré pour développer des effets de menu encore plus puissants comme celui apparaissant (au moment de la rédaction) dans le site Stripe.
Avez-vous déjà créé quelque chose de semblable? Si oui, assurez-vous de partager avec nous les défis que vous avez rencontrés.