Реализация 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 рабочий день.