Lenis
How smooth scroll should be
README
Introduction
This is our take on smooth scroll, lightweight, hard-working, smooth as butter scroll. See Demo.
Features
- 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](https://github.com/locomotivemtl/locomotive-scroll) | [GSAP | [Lenis](https://github.com/studio-freight/lenis) |
---|---|---|---|
|-----------------------------|-------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| | |||
Native | ❌ | ✅ | ✅ |
Native | ❌ | ✅ | ❌ |
Normalize | ✅ | ❌ | ✅ |
Accessibility | ❌ | ❌ | ✅ |
CSS | ❌ | ❌ | ✅ |
IntsersectionObserver | ❌ | ❌ | ✅ |
Open | ✅ | ❌ | ✅ |
Built-in | ✅ | ✅ | ❌ |
Size | [12.1KB](https://bundlephobia.com/package/locomotive-scroll@4.1.4) | [26.08KB](https://bundlejs.com/?q=gsap%2FScrollSmoother&treeshake=%5B%7BScrollSmoother%7D%5D) | [3.6KB](https://bundlephobia.com/package/@studio-freight/lenis@1.0.0-dev.8) |
Installing
using a package manager:
- ```bash
- $ npm i @studio-freight/lenis
- ```
- ```js
- import Lenis from '@studio-freight/lenis'
- ```
using scripts:
- ```htmt
- <script src="https://cdn.jsdelivr.net/gh/studio-freight/lenis@1.0.0/bundled/lenis.js"></script>
- ```
Setup
Basic setup:
- ```js
- const lenis = new Lenis()
- lenis.on('scroll', (e) => {
- console.log(e)
- })
- function raf(time) {
- lenis.raf(time)
- requestAnimationFrame(raf)
- }
- requestAnimationFrame(raf)
- ```
Instance settings
Option | Type | Default | Description |
---|---|---|---|
|----------------------|----------------------|----------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |||
`wrapper` | `HTMLElement|Window` | `window` | The |
`content` | `HTMLElement` | `document.documentElement` | The |
`lerp` | `number` | `0.1` | Linear |
`duration` | `number` | `1.2` | The |
`easing` | `function` | `(t) | The |
`orientation` | `string` | `vertical` | The |
`gestureOrientation` | `string` | `vertical` | The |
`smoothWheel` | `boolean` | `true` | Whether |
`smoothTouch` | `boolean` | `false` | Whether |
`wheelMultiplier` | `number` | `1` | The |
`touchMultiplier` | `number` | `2` | The |
`normalizeWheel` | `boolean` | `true` | Normalize |
`infinite` | `boolean` | `false` | Enable |
<!-- target: goal to reach
- number: value to scroll in pixels
- string: CSS selector or keyword (top, left, start, bottom, right, end)
- HTMLElement: DOM element
options:
- offset(number): equivalent to [scroll-padding-top](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding-top)
- lerp(number): animation lerp intensity
- duration(number): animation duration (in seconds)
- easing(function): animation easing
- immediate(boolean): ignore duration, easing and lerp
- lock(boolean): whether or not to prevent user from scrolling until target reached
- onComplete(function): called when target is reached -->
Instance Methods
Method | Description | Arguments |
---|---|---|
|-----------------------------|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
`raf(time)` | Must | `time`: |
`scrollTo(target, | Scroll | `target`: |
`on(id, | `id` | | |
`stop()` | Pauses | | |
`start()` | Resumes | | |
`destroy()` | Destroys | | |
Instance Events
Event | Callback |
---|---|
|----------|--------------------| | |
`scroll` | Lenis |
Considerations
Make sure scroll-behavior is set to auto or not set at all
- ```css
- .lenis.lenis-smooth {
- scroll-behavior: auto;
- }
- ```
Keep HTML elements default sized, this is necessary for Webflow implementation (see issue)
- ```css
- 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
- ```html
- <div data-lenis-prevent>scroll content</div>
- ```
- ```css
- .lenis.lenis-smooth [data-lenis-prevent] {
- overscroll-behavior: contain;
- }
- ```
Manually use lenis.scrollTo('#anchor') on anchor link click (see issue)
- ```html
- <a href="#anchor" onclick="lenis.scrollTo('#anchor')">scroll to anchor</a>
- ```
Hide overflow when lenis is stopped
- ```css
- .lenis.lenis-stopped {
- overflow: hidden;
- }
- ```
GSAP ScrollTrigger integration
- ```js
- lenis.on('scroll', ScrollTrigger.update)
- gsap.ticker.add((time)=>{
- lenis.raf(time * 1000)
- })
- ```
Limitations
- 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)
Tutorials
Plugins
- r3f-scroll-rig by 14islands
Lenis in use
- 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
- Goodship by Studio Freight
- DeSo by Studio Freight
Authors
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