как реализовать ПЛАВНУЮ смену состояний меню сайта?

Есть условная веб-страница состоящая из нескольких блоков, где блок меню всегда находится в зафиксированном положении - по центру внизу экрана.

По задумке, изначально меню находятся в развёрнутом состоянии, но при начале скролла, оно МЕДЛЕННО сворачивается до одного пункта меню (название текущей страницы), и при наведении на него курсора, меню снова медленно разворачивается. Как это можно реализовать? При данном написанном коде меню слишком быстро сворачивается/разворачивается, буквально за долю секунды, и пока что не совсем понимаю что нужно изменить. И не совсем корректно работает наведение курсора - по идее, как только курсор достигает границы (фона) меню, то оно разворачивается, но по факту, смена состояний происходит при наведении курсора только на текст меню (но не на фон, как надо).

Буду благодарна любой помощи!

const navbar = document.getElementById('navbar');

//Обработчик события прокрутки
window.addEventListener('scroll', function() {
    if (window.scrollY > 50) { // Если прокрутка больше 50 пикселей
        navbar.classList.add('shrink'); // Добавить класс для сворачивания
    } else {
        navbar.classList.remove('shrink'); // Убрать класс, если прокрутка меньше 50 пикселей
    }
});

// Добавляем обработчики событий для наведения
navbar.addEventListener('mouseenter', function() {
    navbar.classList.remove('shrink'); // Убираем класс сворачивания при наведении
});

navbar.addEventListener('mouseleave', function() {
    if (window.scrollY > 50) { // Если прокрутка больше 50 пикселей
        navbar.classList.add('shrink'); // Снова сворачиваем меню
    }
});
body {
    font-family: Arial, Helvetica, sans-serif;
    font-weight: 100;
    margin: 0;
    padding: 0;
}

main {
    display: flex;
    flex-direction: column;
}

.section {
    height: 100vh; /* 100% высоты экрана */
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2rem;
    color: white;
}

footer {
    position: fixed;
    bottom: 3%; /* Отступ от нижней границы экрана */
    left: 50%;
    transform: translateX(-50%); /* Центрирование меню */
    background-color: #f2f2f2;
    padding: 20px;
    border-radius: 30px; /* Скругление углов меню */
    box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
    transition: all 2s ease; /* Плавный переход для всего меню */
}

nav {
    display: inline-block; /* Меню будет иметь ширину, равную содержимому */
}

nav ul {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    transition: max-height 2s ease;  /* Увеличено до 1s для медленного перехода */
    overflow: hidden; /* Скрыть переполнение */
    max-height: 200px; /* Установите максимальную высоту для раскрытого состояния */
}

.shrink {
    max-height: 50px; /* Высота для свёрнутого состояния */
    transition:  2s ease; /* Плавный переход для max-height и opacity */
}

.shrink ul {
    display: flex; /* Отображение только одного пункта */
    justify-content: center;
}

.shrink ul li:not(:first-child) {
    display: none; /* Скрыть все пункты, кроме первого */
}

nav li {
    margin: 0 10px;
}

nav a {
    text-decoration: none;
    color: #333;
    padding: 10px 15px; /* Добавлено для улучшения кликабельности */
    transition: color 0.3s; /* Плавный переход цвета текста */
}

nav ul li a {
    font-family: Arial, Helvetica, sans-serif;
    font-weight: 600;
}

nav a:hover {
    color: #54c8eb; /* Цвет текста при наведении */
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>тест</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main>
        <div class="section" style="background-color: #ff9982;">Блок 1</div>
        <div class="section" style="background-color: #70b47d;">Блок 2</div>
        <div class="section" style="background-color: #99e9f7;">Блок 3</div>
        <div class="section" style="background-color: #fde379;">Блок 4</div>
        <div class="section" style="background-color: #e6acff;">Блок 5</div>
    </main>
    <footer>
        <nav id="navbar">
            <ul id="menu">
                <li><a href="#">Ссылка1</a></li>
                <li><a href="#">Ссылка2</a></li>
                <li><a href="#">Ссылка3</a></li>
                <li><a href="#">Ссылка4</a></li>
                <li><a href="#">Ссылка5</a></li>
            </ul>
        </nav>
    </footer>
    <script src="script.js"></script>
</body>
</html>


Ответы (1 шт):

Автор решения: Vladislav G.

Вариант 1

С сохранением dispalay: none для пунктов списка

const footer = document.querySelector('footer');
const navbar = document.getElementById('navbar');

//Обработчик события прокрутки
window.addEventListener('scroll', function() {
    if (window.scrollY > 50) { // Если прокрутка больше 50 пикселей
        footer.classList.add('shrink'); // Добавить класс для сворачивания
    } else {
        footer.classList.remove('shrink'); // Убрать класс, если прокрутка меньше 50 пикселей
    }
});
body {
    font-family: Arial, Helvetica, sans-serif;
    font-weight: 100;
    margin: 0;
    padding: 0;
}

main {
    display: flex;
    flex-direction: column;
}

.section {
    height: 100vh; /* 100% высоты экрана */
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2rem;
    color: white;
}

footer {
    position: fixed;
    bottom: 3%;
    left: 50%;
    translate: -50% 0;

    padding: 20px;

    width: 100%;
    max-width: 500px;

    background-color: #f2f2f2;
    border-radius: 30px; /* Скругление углов меню */
    box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
    transition: all 2s ease; /* Плавный переход для всего меню */

    &.shrink {
        max-width: 120px;

        ul li:not(:first-child) {
            display: none;
        }

        &:hover {
            max-width: 500px;

            ul li:not(:first-child) {
                display: block;
            }
        }
    }
}

nav ul {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    /* flex-wrap: wrap; */
    justify-content: center;
    gap: 20px;
    transition: max-height 2s ease;  /* Увеличено до 1s для медленного перехода */
    overflow: hidden; /* Скрыть переполнение */
}

/* ------------------------------------------------ */
nav a {
    display: block;

    text-decoration: none;
    color: #333;
    transition: color 0.3s; /* Плавный переход цвета текста */
}

nav ul li a {
    font-family: Arial, Helvetica, sans-serif;
    font-weight: 600;
}

nav a:hover {
    color: #54c8eb; /* Цвет текста при наведении */
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>тест</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main>
        <div class="section" style="background-color: #ff9982;">Блок 1</div>
        <div class="section" style="background-color: #70b47d;">Блок 2</div>
        <div class="section" style="background-color: #99e9f7;">Блок 3</div>
        <div class="section" style="background-color: #fde379;">Блок 4</div>
        <div class="section" style="background-color: #e6acff;">Блок 5</div>
    </main>
    <footer>
        <nav id="navbar">
            <ul id="menu">
                <li><a href="#">Ссылка1</a></li>
                <li><a href="#">Ссылка2</a></li>
                <li><a href="#">Ссылка3</a></li>
                <li><a href="#">Ссылка4</a></li>
                <li><a href="#">Ссылка5</a></li>
            </ul>
        </nav>
    </footer>
    <script src="script.js"></script>
</body>
</html>

Вариант 2

С использованием opacity для пунктов меню. Мне лично этот вариант больше нравится.

const footer = document.querySelector('footer');
const navbar = document.getElementById('navbar');

//Обработчик события прокрутки
window.addEventListener('scroll', function() {
    if (window.scrollY > 50) { // Если прокрутка больше 50 пикселей
        footer.classList.add('shrink'); // Добавить класс для сворачивания
    } else {
        footer.classList.remove('shrink'); // Убрать класс, если прокрутка меньше 50 пикселей
    }
});
body {
    font-family: Arial, Helvetica, sans-serif;
    font-weight: 100;
    margin: 0;
    padding: 0;
}

main {
    display: flex;
    flex-direction: column;
}

.section {
    height: 100vh; /* 100% высоты экрана */
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2rem;
    color: white;
}

footer {
    position: fixed;
    bottom: 3%;
    left: 50%;
    translate: -50% 0;

    padding: 20px;

    width: 100%;
    max-width: 500px;

    background-color: #f2f2f2;
    border-radius: 30px; /* Скругление углов меню */
    box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
    transition: all .4s ease-in; /* Плавный переход для всего меню */

    ul li:not(:first-child) {
        transition: .4s .4s ease-in;
    }

    &.shrink {
        max-width: 120px;
        transition: all .4s .4s ease-in;

        ul li:not(:first-child) {
            opacity: 0;
            transition: .4s ease-in;
        }

        &:hover {
            max-width: 500px;
            transition: all .4s ease-in;

            ul li:not(:first-child) {
                opacity: 1;
                transition: .4s .4s ease-in;
            }
        }
    }
}

nav ul {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    /* flex-wrap: wrap; */
    justify-content: space-around;
    gap: 20px;
    transition: max-height 2s ease;  /* Увеличено до 1s для медленного перехода */
    overflow: hidden; /* Скрыть переполнение */
}

/* ------------------------------------------------ */
nav a {
    display: block;

    text-decoration: none;
    color: #333;
    transition: color 0.3s; /* Плавный переход цвета текста */
}

nav ul li a {
    font-family: Arial, Helvetica, sans-serif;
    font-weight: 600;
}

nav a:hover {
    color: #54c8eb; /* Цвет текста при наведении */
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>тест</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main>
        <div class="section" style="background-color: #ff9982;">Блок 1</div>
        <div class="section" style="background-color: #70b47d;">Блок 2</div>
        <div class="section" style="background-color: #99e9f7;">Блок 3</div>
        <div class="section" style="background-color: #fde379;">Блок 4</div>
        <div class="section" style="background-color: #e6acff;">Блок 5</div>
    </main>
    <footer>
        <nav id="navbar">
            <ul id="menu">
                <li><a href="#">Ссылка1</a></li>
                <li><a href="#">Ссылка2</a></li>
                <li><a href="#">Ссылка3</a></li>
                <li><a href="#">Ссылка4</a></li>
                <li><a href="#">Ссылка5</a></li>
            </ul>
        </nav>
    </footer>
    <script src="script.js"></script>
</body>
</html>

Но тут проблема с центрированием остающегося видимым пункта меню (при сворачивании других). Я специально оставил размер свернутого меню таким, чтобы это было видно. Решений этой проблемы может быть много.Например:

  1. Подстроить размеры свернутого <footer> так, чтобы на глаз ссылка была по центру (тогда проблема с разной длиной имен ссылок);
  2. Использовать для ul свойство justify-content: center, но тогда первый пункт спика скроется из-за overflow: hidden. Тут нужно будет делать так, чтобы видимым оставался только тот пункт списка, который находится посередине. Это несложно сделать, но возникнет проблема с отображением при четном количестве пунктов в списке. Это тоже можно решить с помощью JS, путем дополнения списка еще одним скрытым пунктом до нечетного количества, он может быть скрыт с помощью visibility: hidden;
  3. Можно вообще оставить все так как есть, но как-то обосновать несимметрию в расположении видимого пункта меню. Т.к. первый пункт, который остается видимым, "жмется" к левому краю, а справа остается свободное пространство, в свернутом меню справа можно добавлять иконку а-ля наведи чтобы посмотреть.
→ Ссылка