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.
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.







