Реалізація Scroll Snap для секційного скролу на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Scroll Snap для секційного скролу на сайті
Проста
від 1 робочого дня до 3 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація Scroll Snap для секційного скролу на сайті

CSS Scroll Snap — нативний механізм "примагнічування" скролу до певних позицій без JavaScript. Працює у всіх сучасних браузерах, продуктивно (браузер обробляє на рівні compositor), не потребує бібліотек. Застосовується для лендингів з посекційним скролом, горизонтальних слайдерів, каруселей.

Базова реалізація

/* Вертикальний секційний скролл */
.scroll-container {
  height: 100vh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;    /* обов'язкова привязка */
  scroll-behavior: smooth;
  /* Важливо: убирає momentum scrolling на iOS, потрібен додатковий код */
  -webkit-overflow-scrolling: touch;
}

.section {
  height: 100vh;
  scroll-snap-align: start;         /* привязка до початку секції */
  scroll-snap-stop: always;         /* не можна перелетіти через секцію */
}

/* Горизонтальний слайдер */
.slider-container {
  display: flex;
  overflow-x: scroll;
  scroll-snap-type: x mandatory;
  gap: 16px;
  padding: 0 16px;
  /* Скриваємо scrollbar візуально */
  scrollbar-width: none;
}

.slider-container::-webkit-scrollbar {
  display: none;
}

.slide {
  flex-shrink: 0;
  width: 300px;
  scroll-snap-align: start;
}
// components/SectionScroll.tsx
export function SectionScroll({ sections }: { sections: React.ReactNode[] }) {
  return (
    <div
      className="h-screen overflow-y-scroll"
      style={{
        scrollSnapType: 'y mandatory',
        scrollBehavior: 'smooth',
      }}
    >
      {sections.map((section, i) => (
        <section
          key={i}
          className="h-screen flex items-center justify-center"
          style={{
            scrollSnapAlign: 'start',
            scrollSnapStop: 'always',
          }}
        >
          {section}
        </section>
      ))}
    </div>
  )
}

Навігаційні точки з відстеженням активної секції

// hooks/useActiveSectionScroll.ts
import { useEffect, useRef, useState } from 'react'

export function useActiveSectionScroll(sectionCount: number) {
  const [activeIndex, setActiveIndex] = useState(0)
  const containerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const container = containerRef.current
    if (!container) return

    const handleScroll = () => {
      const { scrollTop, clientHeight } = container
      const index = Math.round(scrollTop / clientHeight)
      setActiveIndex(index)
    }

    container.addEventListener('scroll', handleScroll, { passive: true })
    return () => container.removeEventListener('scroll', handleScroll)
  }, [])

  const scrollToSection = (index: number) => {
    const container = containerRef.current
    if (!container) return
    container.scrollTo({
      top: index * container.clientHeight,
      behavior: 'smooth',
    })
  }

  return { containerRef, activeIndex, scrollToSection }
}
// components/FullPageScroll.tsx
import { useActiveSectionScroll } from '../hooks/useActiveSectionScroll'

const sections = [
  { id: 'hero', label: 'Головна', color: 'bg-blue-600' },
  { id: 'about', label: 'Про нас', color: 'bg-purple-600' },
  { id: 'services', label: 'Послуги', color: 'bg-indigo-600' },
  { id: 'contact', label: 'Контакти', color: 'bg-pink-600' },
]

export function FullPageScroll() {
  const { containerRef, activeIndex, scrollToSection } =
    useActiveSectionScroll(sections.length)

  return (
    <div className="relative">
      {/* Навігаційні точки */}
      <nav className="fixed right-6 top-1/2 -translate-y-1/2 z-50 flex flex-col gap-3">
        {sections.map((section, i) => (
          <button
            key={section.id}
            onClick={() => scrollToSection(i)}
            className={`
              w-3 h-3 rounded-full border-2 border-white transition-all duration-300
              ${activeIndex === i ? 'bg-white scale-125' : 'bg-transparent'}
            `}
            aria-label={`Перейти до ${section.label}`}
          />
        ))}
      </nav>

      {/* Контейнер секцій */}
      <div
        ref={containerRef}
        className="h-screen overflow-y-scroll"
        style={{ scrollSnapType: 'y mandatory' }}
      >
        {sections.map((section, i) => (
          <section
            key={section.id}
            id={section.id}
            className={`h-screen flex items-center justify-center ${section.color}`}
            style={{ scrollSnapAlign: 'start', scrollSnapStop: 'always' }}
          >
            <h2 className="text-5xl font-bold text-white">{section.label}</h2>
          </section>
        ))}
      </div>
    </div>
  )
}

scroll-snap-type: proximity vs mandatory

/* mandatory — завжди привязується, не можна зупинитися між */
scroll-snap-type: y mandatory;

/* proximity — привязується тільки якщо скролл зупинився близько до точки привязки */
scroll-snap-type: y proximity;

Для лендингів рекомендується mandatory. Для довгих сторінок з контентом різної висоти — proximity, інакше користувач не зможе прокрутити до середини розділу.

Горизонтальна карусель з управлінням

// components/CardCarousel.tsx
import { useRef } from 'react'

export function CardCarousel({ cards }: { cards: React.ReactNode[] }) {
  const trackRef = useRef<HTMLDivElement>(null)

  const scrollBy = (direction: 'prev' | 'next') => {
    const track = trackRef.current
    if (!track) return
    const cardWidth = (track.firstElementChild as HTMLElement)?.offsetWidth ?? 300
    track.scrollBy({
      left: direction === 'next' ? cardWidth + 16 : -(cardWidth + 16),
      behavior: 'smooth',
    })
  }

  return (
    <div className="relative">
      <button
        onClick={() => scrollBy('prev')}
        className="absolute left-0 top-1/2 -translate-y-1/2 z-10 bg-white shadow-lg rounded-full w-10 h-10"
      >
        ←
      </button>

      <div
        ref={trackRef}
        className="flex gap-4 overflow-x-scroll px-12"
        style={{ scrollSnapType: 'x mandatory', scrollbarWidth: 'none' }}
      >
        {cards.map((card, i) => (
          <div
            key={i}
            style={{ scrollSnapAlign: 'start', flexShrink: 0, width: 300 }}
          >
            {card}
          </div>
        ))}
      </div>

      <button
        onClick={() => scrollBy('next')}
        className="absolute right-0 top-1/2 -translate-y-1/2 z-10 bg-white shadow-lg rounded-full w-10 h-10"
      >
        →
      </button>
    </div>
  )
}

Типові строки

Базовий секційний скролл з навігаційними точками — 4–6 годин. Горизонтальна карусель + управління + індикатори + адаптив — 1 робочий день.