Lenis is everywhere. You open a luxury brand site, a design agency portfolio, a SaaS homepage — and the scroll feels buttery. Not OS-native bouncy. Eased, linear, intentional. That's Lenis. It hijacks your scroll input, normalizes it across browsers (Safari has always been weird), and applies a spring physics animation to the scroll position. The result: everyone scrolls with the same feel, regardless of OS or input device. Premium sites are using it. Velocity X uses it. And it works — until it doesn't.
Here's the catch: Lenis adds 12kb of JavaScript (gzipped). It intercepts scroll events on every frame. On desktop with a fast CPU, that's fine. On mobile with a thermal budget, it's a battery drain. Worse, if you wire it up wrong, anchor links (`#section`) break because Lenis hijacks the scroll position and the browser gets confused about where to land. Browsers expect native scroll. Lenis pretends to be the browser. Conflicts happen.
Why Native Scroll Feels Janky on Premium Sites
Native browser scroll varies by OS. Windows scroll feels snappy and immediate. macOS scroll has built-in easing and momentum. iOS scroll has spring bounce at edges. Linux scroll is somewhere in between. The moment you open the same site on two devices, the scroll feels different. Your design intent is gone. Users subconsciously notice. They think the site is broken, not realizing the browser is the problem.
Lenis solves this by replacing the browser's scroll mechanism with a custom one. It listens to wheel events (mouse) and touch events (finger), then animates the scroll position using requestAnimationFrame. The animation is frame-locked and deterministic. Every user, every device, same feel. This is why luxury brands love it. The experience is controlled. It's consistent. It feels intentional.
Lenis Setup (5 Lines)
import Lenis from '@studio-freight/lenis';
const lenis = new Lenis({
smoothWheel: true,
smoothTouch: true,
duration: 1.2,
});
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
That's it. Instantiate Lenis, pass your options, and call `raf(time)` on every frame. The `duration` property controls easing speed — 1.2 is the default (1.2 seconds to decelerate). `smoothWheel` hijacks wheel events. `smoothTouch` hijacks touch events. Both default to true. If you want mobile to feel native (no easing), disable `smoothTouch`.
Four Things to Disable on Mobile
Momentum scroll on iOS is sacred. Users expect their finger flick to coast naturally. Lenis's easing breaks that expectation. The scroll feels sluggish. The battery drains. Here's how to detect touch and bail:
const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);
const lenis = new Lenis({
smoothWheel: !isMobile, // disable easing on touch
smoothTouch: false, // let iOS momentum work
duration: 1.2,
});
// Alternative: use media query
const isSmallScreen = window.matchMedia('(max-width: 768px)').matches;
if (isSmallScreen) {
lenis.destroy(); // remove Lenis entirely on mobile
}
Disable `smoothWheel` and `smoothTouch` on mobile. Or destroy Lenis entirely. The tradeoff is small — mobile users get native scroll feel, desktop users get buttery easing. Everyone's happy.
Integration with GSAP ScrollTrigger
If you're using GSAP ScrollTrigger (which you should be), Lenis works but needs a handshake. ScrollTrigger watches scroll position using wheel/touch events. Lenis intercepts those same events and applies easing. Without coordination, ScrollTrigger fires at the wrong scroll positions because it's reading the browser's scroll (which Lenis has decoupled from the actual scroll position).
// Handshake: tell ScrollTrigger about Lenis
lenis.on('scroll', ScrollTrigger.update);
// Optional: refresh ScrollTrigger after Lenis initializes
gsap.registerPlugin(ScrollTrigger);
const lenis = new Lenis({ smoothWheel: true });
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
lenis.on('scroll', ScrollTrigger.update);
window.addEventListener('load', () => ScrollTrigger.refresh());
Call `ScrollTrigger.update()` on every Lenis scroll event. This tells GSAP "hey, scroll position changed, recalculate trigger positions." Without this, pinned sections land at the wrong places and animations fire at the wrong times.
Six FAQs
Does Lenis work with Astro?
Yes. Load it in a client component or inside a `script` tag with `is:inline`. Astro doesn't block scroll hijacking. Just make sure you initialize Lenis AFTER the DOM is ready, not during SSR.
Does it break native scroll restoration?
Yes, but you can restore it manually. When navigating back, the browser tries to scroll to the previous position. Lenis doesn't know about it. You need to listen to `popstate` and call `lenis.scrollTo(savedPosition)` manually. Annoying but doable.
What about anchor links (#section)?
They still work if you wire them right. Add a click handler to all anchor links that calls `lenis.scrollTo(element)` instead of relying on native scroll. Or use the `href` attribute and let Lenis auto-detect. Just test it — broken anchor links kill SEO and user trust.
Does it impact Core Web Vitals?
Marginally. Lenis adds 12kb JS and fires on every scroll event. On fast connections with capable hardware, the CLS (Cumulative Layout Shift) and FID (First Input Delay) stay green. On slow devices or 3G, scroll becomes a bottleneck. Measure with Lighthouse on a slow device before shipping.
Is Lenis better than native scroll + CSS `scroll-behavior`?
Yes for premium feel, no for simplicity. CSS `scroll-behavior: smooth` is zero JS, but it's OS-dependent and jerky. Lenis is deterministic but costs 12kb and a requestAnimationFrame loop. Use Lenis if you're already doing scroll-driven animations (GSAP, Astro View Transitions). Skip it if scroll is passive.
What's the performance hit on 3G?
Real. Lenis is frame-locked. On a 60fps device with a 3G connection, frame drops compound. The scroll feels stuttery. On desktop with fast CPU and fast connection, negligible. Always test on low-end hardware. Velocity X templates disable Lenis on mobile for exactly this reason.
The Bottom Line
Lenis is worth it on desktop. The buttery feel converts. Users notice. It's the difference between "this site feels polished" and "this site feels premium." On mobile, it's a liability. Battery drain, thermal throttling, broken momentum. The smart pattern: detect device, enable on desktop, disable on touch. Get the feel right for your audience, not the entire web.
Want to see Lenis + ScrollTrigger working together in a real site? Check out how GSAP ScrollTrigger pins lock scroll-driven animations. Pair them right and your site doesn't just scroll — it converts.