ScrollToSmooth
Buttery-smooth scroll animations for the web — zero dependencies, fully tree-shakeable, plugin-powered.
Quick Start · API · Plugins · Easings · Live Demo
ScrollToSmooth gives you complete control over scroll animations using requestAnimationFrame. Pick from 31 built-in easing functions, scroll in any direction, queue animations, snap to sections, add touch-momentum — and ship only the code you actually use.
| Feature | What it does | |
|---|---|---|
| 🎯 | Scroll anywhere | Scroll to elements, CSS selectors, pixel offsets, percentages ('50%'), viewport units ('25vh'), or { x, y } coordinates |
| 🎨 | 31 easing functions | From subtle easeOutCubic to bouncy easeOutBounce — import only the ones you need |
| 📐 | Horizontal & 2D scrolling | Scroll on the x-axis, y-axis, or both simultaneously via the Horizontal plugin |
| 📸 | Section snapping | Auto-snap to the nearest section after the user stops scrolling |
| 📱 | Touch momentum | Inertia scrolling on touch devices — swipe and let momentum carry you |
| 🔗 | Scroll queue | Chain multiple scroll targets and execute them in sequence |
| 🧩 | Plugin system | Add only what you need — every plugin is tree-shakeable |
| ⚡ | CSS custom properties | --sts-scroll-y and --sts-scroll-x update every frame — drive progress bars, parallax, or any reactive styling from scroll position |
| 🎛 | Callbacks & events | onScrollStart / onScrollUpdate / onScrollEnd callbacks + scrolltosmooth:* CustomEvents that bubble up |
| 📏 | Smart offsets | Fixed-header offset via element selector, pixels, %, or vh — auto-recalculates on resize |
| 🌐 | Native fallback | Set useNative: true to delegate to the browser's scroll-behavior: smooth when supported |
| 🪶 | Tiny core | ~3 KB min+gzip with zero dependencies |
→ bfiessinger.github.io/scrollToSmooth
See vertical scrolling, horizontal scrolling, 2D board navigation, section snapping, scroll queuing, and progress tracking in action.
npm install scrolltosmoothOr use a CDN — pick the build that fits your needs:
<!-- Minimal core (linear easing only, ~3 KB) -->
<script src="https://cdn.jsdelivr.net/npm/scrolltosmooth/dist/scrolltosmooth.min.js"></script>
<!-- Full bundle with all easings + plugins pre-registered -->
<script src="https://cdn.jsdelivr.net/npm/scrolltosmooth/dist/scrolltosmooth.pkgd.min.js"></script>Tip: The core IIFE only includes the
lineareasing. Use thepkgdbuild for quick prototyping; for production, import individual easings and plugins to keep bundles lean.
import ScrollToSmooth from 'scrolltosmooth'
import { easeOutCubic } from 'scrolltosmooth/easings/easeOutCubic'
const scroller = new ScrollToSmooth('a', {
duration: 600,
easing: easeOutCubic,
offset: '#header', // compensate for a fixed nav
})
scroller.init()That's it — every <a href="#..."> on the page now scrolls smoothly, offset by the height of #header.
// Scroll to an element
scroller.scrollTo('#features')
// Scroll to a pixel position
scroller.scrollTo(500)
// Scroll to 50% of the document
scroller.scrollTo('50%')
// Scroll by a relative offset
scroller.scrollBy(200)Chain targets and let them play one after another:
scroller.queueScroll('#section-1')
scroller.queueScroll('#section-2')
scroller.queueScroll('#section-3')
// Scrolls to each section in sequencePlugins keep the core lean. Import only what you need:
import { HorizontalScrollPlugin } from 'scrolltosmooth/plugins/horizontal'
ScrollToSmooth.use(HorizontalScrollPlugin)
const scroller = new ScrollToSmooth('.nav-link', { axis: 'x' })
scroller.init()
scroller.scrollToX('#slide-3') // horizontal
scroller.scrollToBoth(800, 400) // both axes at onceimport { SnapPlugin } from 'scrolltosmooth/plugins/snap'
ScrollToSmooth.use(SnapPlugin)
const scroller = new ScrollToSmooth('a', {
snap: true,
snapSelector: '.section', // which elements to snap to
snapDebounce: 150, // ms of inactivity before snapping
})
scroller.init()import { TouchMomentumPlugin } from 'scrolltosmooth/plugins/touch-momentum'
ScrollToSmooth.use(TouchMomentumPlugin)
const scroller = new ScrollToSmooth('a', {
touchMomentum: true,
touchMomentumFactor: 300,
touchMomentumMinVelocity: 0.3,
})
scroller.init()31 easing functions, all individually importable:
import { easeOutBounce } from 'scrolltosmooth/easings/easeOutBounce'| Family | In | Out | InOut |
|---|---|---|---|
| Quad | easeInQuad |
easeOutQuad |
easeInOutQuad |
| Cubic | easeInCubic |
easeOutCubic |
easeInOutCubic |
| Quart | easeInQuart |
easeOutQuart |
easeInOutQuart |
| Quint | easeInQuint |
easeOutQuint |
easeInOutQuint |
| Sine | easeInSine |
easeOutSine |
easeInOutSine |
| Expo | easeInExpo |
easeOutExpo |
easeInOutExpo |
| Circ | easeInCirc |
easeOutCirc |
easeInOutCirc |
| Elastic | easeInElastic |
easeOutElastic |
easeInOutElastic |
| Back | easeInBack |
easeOutBack |
easeInOutBack |
| Bounce | easeInBounce |
easeOutBounce |
easeInOutBounce |
Plus linear. Or bring your own: easing: (t) => t * t
ScrollToSmooth writes --sts-scroll-y (and --sts-scroll-x with the Horizontal plugin) to the scroll container on every animation frame. Use them in CSS for zero-JS reactive styling:
/* Parallax background tied to scroll */
.hero-bg {
transform: translateY(calc(var(--sts-scroll-y, 0) * 0.3px));
}
/* Progress bar driven by scroll position */
.progress-bar {
width: calc(var(--sts-scroll-y, 0) / var(--doc-height) * 100%);
}Track every phase of a scroll animation:
const scroller = new ScrollToSmooth('a', {
onScrollStart: ({ startPosition, endPosition }) => {
console.log(`Scrolling from ${startPosition}px to ${endPosition}px`)
},
onScrollUpdate: ({ currentPosition, progress }) => {
progressBar.style.width = `${progress * 100}%`
},
onScrollEnd: ({ endPosition }) => {
analytics.track('scroll_complete', { position: endPosition })
},
})Or use CustomEvents that bubble through the DOM:
document.addEventListener('scrolltosmooth:start', (e) => {
console.log(e.detail.startPosition, '→', e.detail.endPosition)
})CSS smooth scrolling is great for simple use cases. ScrollToSmooth steps in when you need more control:
| Feature | CSS Native | ScrollToSmooth |
|---|---|---|
| Custom easing curves | ❌ | ✅ 31 built-in + custom |
| Scroll lifecycle events | ❌ | ✅ start / update / end |
| Dynamic duration | ❌ | ✅ fixed, relative, min/max |
| Fixed header offset | ✅ via scroll-margin-top | ✅ element, px, %, vh |
| Horizontal / 2D scrolling | ❌ | ✅ via plugin |
| Section snapping | Partial | ✅ customizable debounce |
| Touch momentum | ❌ | ✅ via plugin |
| Scroll queue | ❌ | ✅ chained animations |
| CSS custom properties | ❌ | ✅ per-frame updates |
- Usage Guide — Setup, triggers, offsets, containers, and common patterns
- API Reference — Every method, option, event, and type
- Plugins — Horizontal, Snap, Touch Momentum + writing your own
- Easing Functions — All 31 easings with import examples
- Architecture — Build system, internals, and design decisions
Contributions are welcome! See the architecture docs for project structure and build details.
npm run build # build everything
npm run lint # lint TypeScriptIf you find this project useful, consider supporting it: