This is our take on smooth scroll, lightweight, hard-working, smooth as butter scroll. See Demo.
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/[email protected]/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 |
false |
Whether or not to enable smooth scrolling for mouse wheel events |
smoothTouch |
boolean |
false |
Whether or not to enable smooth scrolling for touch events. |
syncTouch |
boolean |
false |
Mimic touch device scroll while allowing scroll sync (can be unstable on iOS<16) |
syncTouchLerp |
number |
0.1 |
Lerp applied during syncTouch inertia |
touchInertiaMultiplier |
number |
35 |
Manage the the strength of syncTouch inertia |
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 (not reliable atm) |
infinite |
boolean |
false |
Enable infinite scrolling! |
autoResize |
boolean |
true |
Resize instance automatically based on ResizeObserver . If false you must resize manually using .resize() |
Property | Type | Description |
---|---|---|
animatedScroll |
number |
Current scroll value |
dimensions |
object |
Dimensions instance |
direction |
number |
0 : stopped, 1 : scrolling up, -1 : scrolling down |
emitter |
object |
Emitter instance |
options |
object |
Instance options |
targetScroll |
number |
Target scroll value |
time |
number |
Time elapsed since instance creation |
actualScroll |
number |
Current scroll value registered by the browser |
velocity |
number |
Current scroll velocity |
isHorizontal (getter) |
boolean |
Whether or not the instance is horizontal |
isScrolling (getter) |
boolean |
Whether or not the scroll is being animated |
isSmooth (getter) |
boolean |
Whether or not the scroll is animated |
isStopped (getter) |
boolean |
Whether or not the user should be able to scroll |
limit (getter) |
number |
Maximum scroll value |
progress (getter) |
number |
Scroll progress from 0 to 1 |
rootElement (getter) |
HTMLElement |
Element on which Lenis is instanced |
scroll (getter) |
number |
Current scroll value (handles infinite scroll if activated) |
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 | |
resize() |
Compute internal sizes, it has to be used if autoResize option is false . |
|
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;
}
const lenis = new Lenis()
lenis.on('scroll', ScrollTrigger.update)
gsap.ticker.add((time)=>{
lenis.raf(time * 1000)
})
gsap.ticker.lagSmoothing(0)
- 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
- locomotive-scroll by Locomotive
- nuxt-lenis by Milkshake Studio
- Lunchbox by Studio Freight
- Easol 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