От автора: в сегодняшнем уроке мы создадим причудливый hover-эффект в меню. Для этого нам понадобится чуть-чуть CSS и JS. Задача несложная, однако это хорошая возможность попрактиковаться в front-end навыках.
Без лишних слов, давайте взглянем на конечный результат:
Разметка
Начнем мы с самой обычной разметки. Тег nav, в котором будет храниться меню и пустой span:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<nav class="mynav"> <ul> <li> <a href="">Home</a> </li> <li> <a href="">About</a> </li> <li> <a href="">Company</a> </li> <li> <a href="">Work</a> </li> <li> <a href="">Clients</a> </li> <li> <a href="">Contact</a> </li> </ul> </nav> <span class="target"></span> |
CSS
С разметкой покончено, теперь необходимо написать базовые стили:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
.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; } |
Заметьте, что тег span (.target) имеет абсолютное позиционирование. Чуть ниже мы с помощью JS будем определять его точное положение. Также этот элемент должен появляться позади ссылок меню, поэтому он имеет отрицательный z-index.
JavaScript
Теперь давайте перейдем к JS. Для начала необходимо найти нужные элементы, а также создать массив цветов, который нам понадобится позже.
1 2 3 |
const target = document.querySelector(".target"); const links = document.querySelectorAll(".mynav a"); const colors = ["deepskyblue", "orange", "firebrick", "gold", "magenta", "black", "darkblue"]; |
События
Теперь необходимо следить за событиями click и mouseenter на ссылках меню.
При срабатывании события click необходимо отменить перезагрузку страницы. В нашем случае это работает, так как все ссылки у нас с пустым атрибутом href. В реальном проекте ссылки меню будут открывать разные страницы.
Что более важно, по событию mouseenter выполняется колбек-функция mouseenterFunc:
1 2 3 4 |
for (let i = 0; i < links.length; i++) { links[i].addEventListener("click", (e) => e.preventDefault()); links[i].addEventListener("mouseenter", mouseenterFunc); } |
Функция mouseenterFunc
Тело функции mouseenterFunc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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"; } |
Что происходит в теле функции:
к прямому родителю целевой ссылки (li) добавляется класс active;
уменьшается прозрачность opacity всех ссылок меню за исключением активной;
с помощью метода getBoundingClientRect вытягивается размер ссылки и ее положение относительно вьюпорта;
получаем случайный цвет из вышеупомянутого массива и передаем его в виде значения в свойство border-color тега span. Помните, что первоначальное значение свойства установлено в transparent;
извлеченные из метода getBoundingClientRect значения присваиваются соответствующим свойствам тега span. Другими словами, span наследует размер и положение ссылки, на которую навели курсор мыши;
сбрасываем стандартную трансформацию тега span. Это нужно только для первого наведения курсора на ссылку. При первом наведении элемент трансформируется с transform: translateX(-60px) до transform: none. Так мы получаем отличный эффект слайда.
If Active
Нужно заметить, что код выше выполняется каждый раз, когда мы наводим курсор на ссылку. Код выполняется даже тогда, когда мы наводим курсор на активную ссылку. Чтобы избежать этого, код выше мы обернем в условие if:
1 2 3 4 5 |
function mouseenterFunc() { if (!this.parentNode.classList.contains("active")) { // код } } |
Наше демо выглядит следующим образом:
Почти, но не совсем
Вроде бы все работает, так ведь? Нет, если прокрутить страницу или изменить размер вьюпорта и попробовать выбрать ссылку, все поломается. В частности, span будет занимать неправильное положение.
Поиграйтесь с полноэкранным демо (добавьте какой-нибудь контент), чтобы понять, о чем я говорю.
Чтобы таких проблем не возникало, нам необходимо вычислить расстояние, которое мы прокрутили от верхней грани окна и добавить это значение в текущее значение top целевого элемента. Точно так же нужно вычислить расстояние прокрутки по горизонтали (на всякий случай). Полученное значение необходимо добавить в текущее значение left целевого элемента.
Необходимо обновить две строки кода:
1 2 |
const left = this.getBoundingClientRect().left + window.pageXOffset; const top = this.getBoundingClientRect().top + window.pageYOffset; |
Не забывайте, что код выше выполняется сразу, как только браузер обработал DOM и нашел подходящий скрипт. В своем примере, а также в своих дизайнах вы можете запускать этот код только после загрузки страницы. В таком случае вам придется поместить код в обработчик события (например, load).
Вьюпорт
Последнее, что нужно сделать – это проверить, чтобы эффект работал после изменения размера окна браузера. Для этого необходимо следить за событием resize и регистрировать обработчик события resizeFunc.
1 |
window.addEventListener("resize", resizeFunc); |
Тело обработчика:
1 2 3 4 5 6 7 8 9 10 11 |
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`; } } |
Что делает функция сверху:
Ищет пункт меню с классом active. Если такой элемент есть, то мы уже навели курсор на ссылку.
Получает новые свойства left и top активного элемента вместе со свойствами окна и назначает их на тег span. Обратите внимание, что мы получаем только значения свойств, которые изменялись во время события resize. То есть вычислять ширину и высоту ссылок меню заново не нужно.
Поддержка в браузерах
Демо хорошо работает во всех новых браузерах. Если у вас возникли проблемы, пишите мне об этом в комментариях. Если заметили, то мы использовали Babel для компиляции ES6-кода в ES5.
Заключение
В этом уроке мы создали простой, но интересный hover-эффект в меню
Автор: George Martsoukos
Источник: //webdesign.tutsplus.com/
Редакция: Команда webformyself.