Навигация по секциям

В данной статье посмотрим как добиться плавной навигации между секциями, а также напишем JavaScript код, который будет отслеживать активную секцию и в зависимости от этого устанавливать активный класс для соответствующего элемента в блоке навигации

Структура примера

Сделаем несколько секций <section class="section"></section> с разным фоном для наглядности

Для навигации каждой секции назначаем уникальный идентификатор <section class="section" id="section-a"></section>

Для отслеживания активной секции добавим data-атрибуты data-section - <section class="section" data-section="a" id="section-a"></section>

В блоке навигации <nav class="nav"></nav> у элементов li будут соответствующие data-атрибуты - <li class="nav__li" data-section="a"></li>

Cсылки в блоке навигации будут иметь атрибут href соответствующий идентификаторам секций - <li class="nav__li" data-section="a"><a href="#section-a">Orange Peel</a></li>

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Practice</title>
    <link rel="stylesheet" href="css/bootstrap-reboot.min.css"/>
    <link rel="preconnect" href="https://fonts.gstatic.com"/>
    <link href="https://fonts.googleapis.com/css2?family=Lora:wght@700&amp;family=Roboto:wght@300;400&amp;display=swap" rel="stylesheet"/>
    <link rel="stylesheet" href="css/main.css"/>
  </head>
  <body>
    <section class="section" style="background-color:#FF9F1C" data-section="a" id="section-a">
      <h2 class="h2">Orange Peel</h2><img class="image" src="https://picsum.photos/512/256" alt=""/>
      <p class="text">Ullam repudiandae? Natus quas omnis vitae alias quam assumenda blanditiis vel, aut facere, labore eius corrupti incidunt aliquam sapiente molestiae ipsam libero placeat.</p>
    </section>
    <section class="section" style="background-color:#FDFFFC" data-section="b" id="section-b">
      <h2 class="h2">Baby Powder</h2><img class="image" src="https://picsum.photos/512/257" alt=""/>
      <p class="text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deleniti fugit tempora, culpa sequi vitae, omnis aut incidunt consectetur eum nihil et voluptatibus neque quis illum accusantium, explicabo ullam repudiandae? Natus quas omnis vitae alias quam assumenda blanditiis vel, aut facere, labore eius corrupti incidunt aliquam sapiente molestiae ipsam libero placeat.</p>
    </section>
    <section class="section" style="background-color:#41EAD4" data-section="c" id="section-c">
      <h2 class="h2">Turquoise</h2><img class="image" src="https://picsum.photos/512/258" alt=""/>
      <p class="text">Omnis aut incidunt consectetur eum nihil et voluptatibus neque quis illum accusantium, explicabo ullam repudiandae? Natus quas omnis vitae alias quam assumenda blanditiis vel, aut facere, labore eius corrupti incidunt aliquam sapiente molestiae ipsam libero placeat.</p>
    </section>
    <section class="section section_dark" style="background-color:#F71735" data-section="d" id="section-d">
      <h2 class="h2">Imperial Red</h2><img class="image" src="https://picsum.photos/512/259" alt=""/>
      <p class="text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deleniti fugit tempora, culpa sequi vitae, omnis aut incidunt consectetur eum nihil et voluptatibus neque quis illum accusantium, explicabo ullam repudiandae? Natus quas omnis vitae alias quam assumenda blanditiis vel, aut facere, labore eius corrupti incidunt aliquam sapiente molestiae ipsam libero placeat.</p>
    </section>
    <section class="section section_dark" style="background-color:#011627" data-section="e" id="section-e">
      <h2 class="h2">Rich Black FOGRA 29</h2><img class="image" src="https://picsum.photos/512/260" alt=""/>
      <p class="text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deleniti fugit tempora, culpa sequi vitae, omnis aut incidunt consectetur eum nihil et voluptatibus neque quis illum accusantium, explicabo ullam repudiandae?</p>
    </section>
    <nav class="nav">
      <ul>
        <li class="nav__li" data-section="a"><a href="#section-a">Orange Peel</a></li>
        <li class="nav__li" data-section="b"><a href="#section-b">Baby Powder</a></li>
        <li class="nav__li" data-section="c"><a href="#section-c">Turquoise</a></li>
        <li class="nav__li" data-section="d"><a href="#section-d">Imperial Red</a></li>
        <li class="nav__li" data-section="e"><a href="#section-e">Rich Black FOGRA 29</a></li>
      </ul>
    </nav>
    <script src="js/main.js"></script>
  </body>
</html>


CSS

Чтобы не усложнять пример, для плавного скролла между секциями будем использовать CSS свойство scroll-behavior: smooth;, которое поддерживается в браузерах Chrome, Firefox, Edge

Если необходима поддержка плавного скролла в Safari, можно воспользоваться следующим решением - https://htmldom.dev/scroll-to-an-element-smoothly

body {
  scroll-behavior: smooth; /* Добавляем плавный скролл между секциями */
  font-family: 'Roboto', sans-serif;
  background: #fff;
  color: #011627;
  position: relative;
}
.container {
  max-width: 1120px;
  padding: 0 16px;
  margin: 0 auto;
}
.section {
  min-height: 100vh; /* Делаем секции во всю высоту браузера */
  padding: 48px 24px;
}
.section_dark {
  color: #fdfffc;
}
.h2 {
  font-size: 48px;
  font-family: 'Lora', serif;
  text-align: center;
  margin-bottom: 48px;
}
.image {
  max-height: 256px;
  max-width: 512px;
  width: 100%;
  margin: 0 auto;
  display: block;
  margin-bottom: 48px;
  border-radius: 32px;
}
.image img {
  height: 100%;
  -o-object-fit: cover;
  object-fit: cover;
  display: block;
  width: 100%;
}
.text {
  max-width: calc(512px - 64px);
  margin: 0 auto;
  text-align: center;
}
.nav {
  position: fixed; /* фиксируем блок навигации */
  top: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  right: 0;
  z-index: 999;
  padding-right: 16px;
  text-align: right;
}
.nav ul {
  padding: 0;
  margin: 0;
  list-style: none;
}
.nav__li a {
  font-size: 12px;
  text-transform: uppercase;
  text-decoration: none;
  color: #011627;
  transition: 0.24s;
}
.nav__li_active a {
  font-weight: 700;
}
.nav_light a {
  color: #fdfffc;
}

/* Немного стилей для адаптивности */
@media (max-width: 991.98px) {
  .section {
    padding-right: 48px;
  }
  .nav__li {
    text-align: center;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 20px;
  }
  .nav__li a {
    width: 16px;
    height: 16px;
    overflow: hidden;
    border-radius: 50%;
    display: block;
    color: transparent;
    background: #011627;
  }
  .nav__li_active a {
    width: 8px;
    height: 8px;
  }
  .nav_light .nav__li a {
    background: #fdfffc;
  }
}


Пишем JavaScript

Логика следующая - при загрузке/скролле/ресайзе страницы, будем проверять, если страница прокручена больше, чем расстояние секции от начала страницы, то соответствующей ссылке навигации добавляем активный класс

Код с комментариями

document.addEventListener('DOMContentLoaded', function() {

  const navInit = () => {
    const nav = document.querySelector('.nav') // ищем блок навигации
    const links = document.querySelectorAll('.nav__li') // ищем все навигационные ссылки
    const sections = document.querySelectorAll('.section') // ищем все секции
    sections.forEach(section => { // для каждой секции
      if (window.pageYOffset >= section.offsetTop) { // проверяем, если страница прокручена больше, чем расстояние секции от начала страницы
        links.forEach(link => { // для каждой ссылки
          link.classList.remove('nav__li_active') // удаляем активный класс
          if (link.dataset.section === section.dataset.section) { // проверяем, если data-атрибуты ссылки и секции совпадают
            link.classList.add('nav__li_active') // добавляем ссылке активный класс
          }

        })
      }

    })
  }
  navInit() // запускаем функцию при загрузке страницы
  window.addEventListener('scroll', () => {
    navInit() // запускаем функцию при скролле страницы
  })
  window.addEventListener('resize', () => {
    navInit() // запускаем функцию при ресайзе страницы
  })

})


Добавим возможность автоматического изменения цвета, если секция имеет темный фон

document.addEventListener('DOMContentLoaded', function() {

  const navInit = () => {
    const nav = document.querySelector('.nav') // ищем блок навигации
    const links = document.querySelectorAll('.nav__li') // ищем все навигационные ссылки
    const sections = document.querySelectorAll('.section') // ищем все секции
    sections.forEach(section => { // для каждой секции
      if (window.pageYOffset >= section.offsetTop) { // проверяем, если страница прокручена больше, чем расстояние секции от начала страницы
        links.forEach(link => { // для каждой ссылки
          link.classList.remove('nav__li_active') // удаляем активный класс
          if (link.dataset.section === section.dataset.section) { // проверяем, если data-атрибуты ссылки и секции совпадают
            link.classList.add('nav__li_active') // добавляем ссылке активный класс

            if (section.classList.contains('section_dark')) { // если активная секция имеет класс section_dark, то есть у нее темный фон
              nav.classList.add('nav_light') // меняем цвет ссылок на светлый, добавляя класс nav_light
            } else { // иначе
              nav.classList.remove('nav_light') // удаляем класс nav_light
            }

          }

        })
      }

    })
  }
  navInit() // запускаем функцию при загрузке страницы
  window.addEventListener('scroll', () => {
    navInit() // запускаем функцию при скролле страницы
  })
  window.addEventListener('resize', () => {
    navInit() // запускаем функцию при ресайзе страницы
  })

})

Результат можно посмотреть здесь

Если желаете увеличить производительность данного кода, можете ознакомиться со статьей - Повышаем производительность при resize, scroll, …



Буду рад, если статья оказалась полезной

Спасибо за ваше внимание и уделённое время!