This is our take on smooth scroll, lightweight, hard-working, smooth as butter scroll. See Demo.
- Run scroll in the main thread
- Performant
- Lightweight (<4Kb gzipped)
- Keep CSS Sticky and IntersectionObserver
- Accessibility (CMD+F page search, keyboard navigation, keep scroll position on page refresh, etc.)
- External RAF
- SSR proof
- Custom scroll easing and duration
Feature | Locomotive-scroll | GSAP ScrollSmoother | Lenis |
---|---|---|---|
Native scrollbar | ❌ | ✅ | ✅ |
Native "scroll" inputs | ❌ | ✅ | ❌ |
Accessibility | ❌ | ❌ | ✅ |
CSS Sticky | ❌ | ❌ | ✅ |
IntersectionObserver | ❌ | ❌ | ✅ |
Open source | ✅ | ❌ | ✅ |
Built-in animation system | ✅ | ✅ | ❌ |
Size (gzip) | 12.1KB | 32.11KB | 3.6KB |
using a package manager:
$ npm i @studio-freight/lenis
import Lenis from '@studio-freight/lenis'
using scripts:
<script src="https://cdn.jsdelivr.net/gh/studio-freight/lenis@1/bundled/lenis.min.js"></script>
Basic setup:
const lenis = new Lenis()
lenis.on('scroll', (e) => {
console.log(e)
})
function raf(time) {
lenis.raf(time)
requestAnimationFrame(raf)
}
requestAnimationFrame(raf)
Option | Type | Default | Description |
---|---|---|---|
wrapper |
HTMLElement, Window |
window |
The element that will be used as the scroll container |
content |
HTMLElement |
document.documentElement |
The element that contains the content that will be scrolled, usually wrapper 's direct child |
wheelEventsTarget |
HTMLElement, Window |
wrapper |
The element that will listen to wheel events |
lerp |
number |
0.1 |
Linear interpolation (lerp) intensity (between 0 and 1) |
duration |
number |
1.2 |
The duration of scroll animation (in seconds). Useless if lerp defined |
easing |
function |
(t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)) |
The easing function to use for the scroll animation, our default is custom but you can pick one from Easings.net. Useless if lerp defined |
orientation |
string |
vertical |
The orientation of the scrolling. Can be vertical or horizontal |
gestureOrientation |
string |
vertical |
The orientation of the gestures. Can be vertical , horizontal or both |
smoothWheel |
boolean |
true |
Whether or not to enable smooth scrolling for mouse wheel events |
smoothTouch |
boolean |
false |
Whether or not to enable smooth scrolling for touch events. Note: We have disabled it by default because touch devices' native smoothness is impossible to mimic |
wheelMultiplier |
number |
1 |
The multiplier to use for mouse wheel events |
touchMultiplier |
number |
2 |
The multiplier to use for touch events |
normalizeWheel |
boolean |
false |
Normalize wheel inputs across browsers |
infinite |
boolean |
false |
Enable infinite scrolling! |
Method | Description | Arguments |
---|---|---|
raf(time) |
Must be called every frame for internal usage. | time : in ms |
scrollTo(target, options) |
Scroll to target. | target : goal to reach
options
|
on(id, function) |
id can be any of the following instance events to listen. |
|
stop() |
Pauses the scroll | |
start() |
Resumes the scroll | |
destroy() |
Destroys the instance and removes all events. |
Event | Callback Arguments |
---|---|
scroll |
Lenis instance |
html.lenis {
height: auto;
}
.lenis.lenis-smooth {
scroll-behavior: auto;
}
.lenis.lenis-smooth [data-lenis-prevent] {
overscroll-behavior: contain;
}
.lenis.lenis-stopped {
overflow: hidden;
}
.lenis.lenis-scrolling iframe {
pointer-events: none;
}
.lenis.lenis-smooth {
scroll-behavior: auto;
}
Keep HTML elements default sized, this is necessary for Webflow implementation (see issue)
html.lenis {
height: auto;
}
Use the data-lenis-prevent
attribute on nested scroll elements. In addition, we advise you to add overscroll-behavior: contain
on this element
<div data-lenis-prevent>scroll content</div>
.lenis.lenis-smooth [data-lenis-prevent] {
overscroll-behavior: contain;
}
Manually use lenis.scrollTo('#anchor')
on anchor link click (see issue)
<a href="#anchor" onclick="lenis.scrollTo('#anchor')">scroll to anchor</a>
.lenis.lenis-stopped {
overflow: hidden;
}
lenis.on('scroll', ScrollTrigger.update)
gsap.ticker.add((time)=>{
lenis.raf(time * 1000)
})
- no support for CSS scroll-snap
- capped to 60fps on Safari (source)
- smooth scroll will stop working over iframe since they don't forward wheel events
- position fixed seems to lag on MacOS Safari pre-M1 (source)
- Scroll Animation Ideas for Image Grids by Codrops
- How to Animate SVG Shapes on Scroll by Codrops
- The BEST smooth scrolling library for your Webflow website! (Lenis) by Diego Toda de Oliveira
- Easy smooth scroll in @Webflow with Lenis + GSAP ScrollTrigger tutorial by También Studio
- Loconative-scroll by Quentin Hocde
- react-lenis by Studio Freight
- r3f-scroll-rig by 14islands
- Lenis Scroll Snap Plugin by Funkhaus
- Wyre by Studio Freight
- Lunchbox by Studio Freight
- Easol by Studio Freight
- Repeat by Studio Freight
- Dragonfly by Studio Freight
- Yuga Labs by Antinomy Studio
- Quentin Hocde's Portfolio by Quentin Hocde
- Houses Of by Félix P. & Shelby Kay
- Shelby Kay's Portfolio by Shelby Kay
- Heights Agency Portfolio by Francesco Michelini
- Goodship by Studio Freight
- Flayks' Portfolio by Félix P. & Shelby Kay
- Matt Rothenberg's portfolio by Matt Rothenberg
- Edoardo Lunardi's portfolio by Edoardo Lunardi
- DeSo by Studio Freight
This set of hooks is curated and maintained by the Studio Freight Darkroom team:
- Clément Roche (@clementroche_) – Studio Freight
- Guido Fier (@uido15) – Studio Freight
- Leandro Soengas (@lsoengas) - Studio Freight
- Franco Arza (@arzafran) - Studio Freight