LCP on mobile suffers from discrete scrolling — users see janky parallax and low FPS. Solution: Lenis or Locomotive Scroll. Our experience: in 50+ projects we achieved stable 60 FPS even on iPhone SE. Without these libraries, smooth scrolling looks choppy and lacks inertia. For a cinematic effect, proper integration is required — we'll show how to avoid typical mistakes.
Clients increasingly request cinematic scrolling — it's a web trend. Our team, with full-stack development experience of over 5 years, has integrated both libraries in dozens of projects: from landing pages to complex SPAs. Here's how to do it right.
Which library to choose: Lenis or Locomotive Scroll?
The first dilemma is library selection. Lenis is 3 times lighter than Locomotive Scroll (5 KB vs 15 KB in gzip), actively maintained in the Lenis repository, and not tied to a specific HTML structure. According to documentation, the library is optimized for modern bundlers. Locomotive Scroll v2 is appropriate if you need built-in parallax without additional libraries, but for custom animations Lenis gives more control. FPS on mobile is 1.5-2 times higher with Lenis (60 FPS vs 30-40 FPS). Significant license budget savings compared to commercial alternatives.
| Feature | Lenis | Locomotive Scroll v2 |
|---|---|---|
| Size (min+gzip) | ~5 KB | ~15 KB |
| Built-in parallax | No (via ScrollTrigger) | Yes (data-speed) |
| React integration | Simple (context) | Requires wrapper |
| Maintenance | Active (Darkroom) | Slowed down |
| iOS smooth touch | Disabled via option | Built-in fallback |
| FPS on mobile | 60 FPS | 30-40 FPS |
How to install Lenis and start smooth scrolling
Installation via package manager:
npm install lenis Creating an instance with settings:
import Lenis from 'lenis'; const lenis = new Lenis({ duration: 1.2, easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), orientation: 'vertical', smoothWheel: true, touchMultiplier: 2, }); function raf(time: number) { lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); How to integrate Lenis with GSAP ScrollTrigger
This is the main use case: Lenis manages scroll, ScrollTrigger manages animations. After creating Lenis, subscribe to the scroll event and update ScrollTrigger. Replace the default GSAP ticker with a call to lenis.raf.
import gsap from 'gsap'; import ScrollTrigger from 'gsap/ScrollTrigger'; gsap.registerPlugin(ScrollTrigger); lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add((time) => { lenis.raf(time * 1000); }); gsap.ticker.lagSmoothing(0); Thanks to this, ScrollTrigger animations are synchronized with the virtual scroll. Saves up to 2 hours of debugging typical desync bugs.
How to integrate Lenis with React
React integration requires a context and hook. Create a LenisProvider and use useLenis for programmatic scrolling. This simplifies React smooth scroll implementation.
import { createContext, useContext, useEffect, useRef } from 'react'; import Lenis from 'lenis'; const LenisContext = createContext<Lenis | null>(null); export const LenisProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const lenisRef = useRef<Lenis | null>(null); useEffect(() => { const lenis = new Lenis(); lenisRef.current = lenis; const raf = (time: number) => lenis.raf(time); requestAnimationFrame(raf); return () => { lenis.destroy(); }; }, []); return <LenisContext.Provider value={lenisRef.current}>{children}</LenisContext.Provider>; }; export const useLenis = () => useContext(LenisContext); Important: create the instance inside useEffect and call destroy on unmount.
What to do with typical errors
Here's a step-by-step guide for solving common problems:
- Dynamic content: after adding elements via fetch or React state, call
lenis.resize()orScrollTrigger.refresh(). - Nested scrollable containers: exclude modals from virtual scroll by adding the attribute
data-lenis-prevent. - Mobile devices: disable
smoothTouchon iOS (optionsmoothTouch: false) and usewill-change: transformon parallax elements for forced GPU acceleration. - Performance: track FPS with
lenis.on('scroll', ...). If on weak devices fps drops below 50, reducedurationparameter to 0.8 or disable smoothness for older Android versions.
Common mistake: N+1 query with parallax
When dynamically updating content with parallax effects without calling `lenis.resize()`, an N+1 query occurs — each new element triggers a separate scroll recalculation. This drops FPS to 20. Solution: use ResizeObserver or Batch updates.Comparison of integration time: custom solution vs Lenis
| Approach | Setup time | Typical bugs | Mobile support |
|---|---|---|---|
| Custom smooth scroll | 3-5 days | N+1, desync | Requires extra work |
| Lenis | 1-3 days | Minimal | Out of the box |
What's included in the integration
- Selection and configuration of the library (Lenis / Locomotive) for the project architecture.
- Integration with GSAP ScrollTrigger and custom animations (including timelines).
- Parallax effects via data-attributes or custom calculations.
- Adaptation for mobile devices (iOS/Android) considering screen resolutions.
- Performance problem solving (ResizeObserver, will-change, compositing optimization).
- Code documentation and team training.
- Technical support for 30 days after delivery.
Why choose Lenis for a new project?
Our experience shows: Lenis solves 90% of smooth scrolling tasks without unnecessary magic. It easily combines with any animation library (GSAP, Framer Motion, Three.js). We guarantee that turnkey integration will take no more than 3 days, including mobile adaptation and custom parallax effects. Budget savings compared to a custom solution are up to 40%. If you have a complex project with nested scrollable containers or dynamic loading, contact us — we'll evaluate the task for free.
Timeline and cost
Basic Lenis setup with smooth scrolling — from 1 day. Full integration with parallax, ScrollTrigger, and React — 2-3 days. Cost is calculated individually based on complexity and scope. Order integration today — get a consultation from an engineer with over 5 years of experience.







