Реалізація Locomotive Scroll / Lenis для плавного скролу

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

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

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Locomotive Scroll / Lenis для плавного скролу
Середня
від 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

Реалізація Locomotive Scroll / Lenis для плавного скролу

Locomotive Scroll та Lenis — дві бібліотеки, які реалізують «маслистий» скролл: скролиться не сторінка, а відбувається інтерполяція між поточною та цільовою позицією. Результат — кінематографічний рух, який можна синхронізувати з GSAP ScrollTrigger.

Вибір між ними: Lenis простіший, легший, активно підтримується (Darkroom/Studio Freight). Locomotive Scroll v2 важче, але має вбудований parallax через data-speed. Для нових проектів Lenis — переважаючий вибір.

Lenis: базова інсталяція

npm install lenis
import Lenis from 'lenis'

const lenis = new Lenis({
  duration: 1.2,           // тривалість одного "кроку" скролу
  easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),  // expo out
  orientation: 'vertical',
  gestureOrientation: 'vertical',
  smoothWheel: true,
  touchMultiplier: 2,      // чутливість на touch
  infinite: false,
})

// RAF loop — Lenis вимагає вызову raf() кожний кадр
function raf(time: number) {
  lenis.raf(time)
  requestAnimationFrame(raf)
}

requestAnimationFrame(raf)

Інтеграція Lenis + GSAP ScrollTrigger

Це основний варіант використання: Lenis управляє скроллом, ScrollTrigger — анімаціями привязаними до позиції.

import Lenis from 'lenis'
import gsap from 'gsap'
import ScrollTrigger from 'gsap/ScrollTrigger'

gsap.registerPlugin(ScrollTrigger)

const lenis = new Lenis()

// Критично: без цього ScrollTrigger буде працювати по нативному scrollY,
// а не по віртуальному скролу Lenis
lenis.on('scroll', ScrollTrigger.update)

gsap.ticker.add((time) => {
  lenis.raf(time * 1000)  // gsap ticker дає час у секундах
})

gsap.ticker.lagSmoothing(0)  // вимкнути lag smoothing GSAP

// Тепер ScrollTrigger працює коректно з Lenis
gsap.to('.hero-title', {
  scrollTrigger: {
    trigger: '.hero',
    start: 'top top',
    end: 'bottom top',
    scrub: 1,
  },
  y: -100,
  opacity: 0,
})

Parallax через Lenis

Lenis сам по собі не робить parallax — тільки плавний скролл. Parallax-ефекти будуються поверху через ScrollTrigger або кастомний RAF:

// Parallax без GSAP — прямого оновлення transform
const parallaxItems = document.querySelectorAll<HTMLElement>('[data-parallax]')

lenis.on('scroll', ({ scroll }) => {
  parallaxItems.forEach((el) => {
    const speed = parseFloat(el.dataset.parallax || '0.3')
    const rect = el.getBoundingClientRect()
    const center = rect.top + rect.height / 2 - window.innerHeight / 2
    el.style.transform = `translateY(${center * speed}px)`
  })
})

Locomotive Scroll v2

npm install locomotive-scroll
import LocomotiveScroll from 'locomotive-scroll'
import 'locomotive-scroll/dist/locomotive-scroll.css'

const scroll = new LocomotiveScroll({
  el: document.querySelector('[data-scroll-container]') as HTMLElement,
  smooth: true,
  multiplier: 1,
  lerp: 0.08,          // коефіцієнт інтерполяції (менше — плавніше)
  smartphone: {
    smooth: false,     // вимкнути на мобілці (нативний скролл)
  },
  tablet: {
    smooth: false,
  },
})

// Необхідно вызивати при зміні висоти контенту
scroll.update()

HTML-структура для Locomotive:

<main data-scroll-container>
  <section data-scroll-section>
    <h1 data-scroll data-scroll-speed="2">Заголовок</h1>

    <!-- Sticky елемент -->
    <div data-scroll data-scroll-sticky data-scroll-target="#section">
      Sticky sidebar
    </div>

    <!-- Parallax зображення -->
    <img
      data-scroll
      data-scroll-speed="-1"
      data-scroll-position="top"
      src="photo.jpg"
    />
  </section>
</main>

React-інтеграція Lenis

import { useEffect, useRef } from 'react'
import Lenis from 'lenis'

// Singleton через React context
import { createContext, useContext } from 'react'

const LenisContext = createContext<Lenis | null>(null)

export function LenisProvider({ children }: { children: React.ReactNode }) {
  const lenisRef = useRef<Lenis | null>(null)

  useEffect(() => {
    const lenis = new Lenis({
      duration: 1.2,
      easing: (t) => Math.min(1, 1.001 - 2 ** (-10 * t)),
    })

    lenisRef.current = lenis

    let rafId: number
    function raf(time: number) {
      lenis.raf(time)
      rafId = requestAnimationFrame(raf)
    }
    rafId = requestAnimationFrame(raf)

    return () => {
      cancelAnimationFrame(rafId)
      lenis.destroy()
    }
  }, [])

  return (
    <LenisContext.Provider value={lenisRef.current}>
      {children}
    </LenisContext.Provider>
  )
}

export function useLenis() {
  return useContext(LenisContext)
}

// Програмна прокрутка з будь-якого компонента
function NavLink({ href }: { href: string }) {
  const lenis = useLenis()

  const handleClick = (e: React.MouseEvent) => {
    e.preventDefault()
    const target = document.querySelector(href)
    if (target && lenis) {
      lenis.scrollTo(target as HTMLElement, {
        offset: -80,
        duration: 1.5,
      })
    }
  }

  return <a href={href} onClick={handleClick}>...</a>
}

Зупинка/возобновлення скролу

Потрібно для модальних вікон, меню-оверлеїв:

// Lenis
lenis.stop()  // заблокувати скролл
lenis.start() // розблокувати

// Locomotive Scroll
scroll.stop()
scroll.start()

Продуктивність та pitfalls

ResizeObserver — обидва рішення відслідковують висоту контенту через ResizeObserver. При динамічному завантаженню контенту (lazy images, аккордеони) потрібно вызивати lenis.resize() або scroll.update() після змін.

iOS Safari — на iOS нативний scroll має особливу фізику (bounce). Lenis з smoothTouch: false (за замовчуванням) залишає touch-скролл нативним, що правильно.

Вкладені скроллюючі контейнери — модалки, сайдбари зі своїм overflow. Потрібно зупиняти Lenis при вході в такий контейнер.

will-change: transform на parallax-елементах — переводить їх на окремий compositor layer, знижує перерахунок layout.

Терміни

Lenis з базовим скроллом та ScrollTrigger анімаціями — 1 день. Locomotive Scroll з parallax-даними, мобільним fallback та React-інтеграцією — 2–3 дні.