This guide covers everything you need to get ScrollToSmooth running — from basic link hijacking to advanced patterns like scroll queuing, custom containers, and dynamic offsets.
- Basic Setup
- Choosing an Easing
- Custom Trigger Elements
- Scroll Targets
- Programmatic Scrolling
- Scroll Queue
- Fixed Header Offsets
- Custom Scroll Containers
- Duration Control
- Native Browser Fallback
- Tracking Scroll Progress
- CSS Custom Properties
- Updating & Destroying
import ScrollToSmooth from 'scrolltosmooth'
const scroller = new ScrollToSmooth('a', {
duration: 400,
})
scroller.init()The first argument is a CSS selector for the elements that should trigger smooth scrolling. When a user clicks a matching element, ScrollToSmooth reads its href attribute, resolves the target, and animates the scroll.
The core ships with only linear. Import your preferred easing to keep the bundle small:
import ScrollToSmooth from 'scrolltosmooth'
import { easeOutCubic } from 'scrolltosmooth/easings/easeOutCubic'
const scroller = new ScrollToSmooth('a', {
duration: 600,
easing: easeOutCubic,
})
scroller.init()If you need to resolve an easing by name at runtime (e.g., from user config):
import { getEasing } from 'scrolltosmooth/easings'
const scroller = new ScrollToSmooth('a', {
easing: getEasing('easeOutCubic'),
})Or use an inline function:
new ScrollToSmooth('a', {
easing: (t) => t * t, // custom quadratic easing
})See the full easing reference for all 31 available easing functions.
Triggers don't have to be anchor tags. Any element can trigger a scroll — just tell ScrollToSmooth which attribute holds the target:
<button data-scrollto="#features">Jump to Features</button>
<span data-scrollto="#pricing">See Pricing</span>new ScrollToSmooth('[data-scrollto]', {
targetAttribute: 'data-scrollto',
})When a trigger's target is # (empty hash), ScrollToSmooth scrolls to the top of the container by default. Disable this with:
new ScrollToSmooth('a', {
topOnEmptyHash: false,
})ScrollToSmooth accepts many target types for maximum flexibility:
| Target | Example | What happens |
|---|---|---|
| CSS selector | '#features' |
Scrolls to the matched element |
| Element | document.getElementById('features') |
Scrolls to the element |
| Pixel number | 500 |
Scrolls to 500px from the top |
| Pixel string | '500' |
Same as above |
| Percentage | '50%' |
Scrolls to 50% of the document height |
| Viewport height | '25vh' |
Scrolls to 25% of the viewport height |
| Coordinate object | { x: 800, y: 400 } |
Scrolls to exact x/y position (requires Horizontal plugin) |
You don't need click triggers to scroll. Use the API directly:
// Scroll to an element
scroller.scrollTo('#contact')
// Scroll to a pixel position
scroller.scrollTo(1200)
// Scroll to 75% of the document
scroller.scrollTo('75%')
// Scroll by a relative offset (negative = scroll up)
scroller.scrollBy(300)
scroller.scrollBy(-150)
// Cancel an active animation
scroller.cancelScroll()Calling scrollTo() while an animation is running will cancel the current animation and immediately start the new one.
Queue multiple targets and they'll execute in order, one after another:
scroller.queueScroll('#intro')
scroller.queueScroll('#features')
scroller.queueScroll('#pricing')
scroller.queueScroll('#footer')This scrolls to #intro first, then #features, then #pricing, and finally #footer — each animation starting when the previous one ends.
Give queue items an id so you can selectively remove them:
scroller.queueScroll('#section-a', 'tour-step-1')
scroller.queueScroll('#section-b', 'tour-step-2')
scroller.queueScroll('#section-c', 'tour-step-3')
// Remove just one item
scroller.clearQueue('tour-step-2')
// Clear the entire queue
scroller.clearQueue()// Cancel animation but keep pending queue items
scroller.cancelScroll()
// Cancel animation AND discard the queue
scroller.cancelScroll(true)When your layout has a sticky or fixed header, you need an offset so scroll targets aren't hidden behind it.
Automatically uses the element's height — adjusts if the header resizes:
new ScrollToSmooth('a', {
offset: '#header',
})new ScrollToSmooth('a', {
offset: 80, // 80px
})A percentage of the document height:
new ScrollToSmooth('a', {
offset: '5%',
})new ScrollToSmooth('a', {
offset: '10vh', // 10% of viewport height
})Percent and viewport offsets are recalculated automatically on window resize.
By default, ScrollToSmooth scrolls the document. For scrollable container elements, pass a container:
new ScrollToSmooth('.sidebar-link', {
container: '#sidebar',
duration: 300,
})The container can be a CSS selector string or a DOM element reference.
Every animation takes the same time regardless of distance:
new ScrollToSmooth('a', { duration: 600 })Scale the duration based on the scroll distance — longer distances get more time:
new ScrollToSmooth('a', {
duration: 400,
durationRelative: true, // 400ms per 1000px of distance
})Use a custom divisor:
new ScrollToSmooth('a', {
duration: 400,
durationRelative: 500, // 400ms per 500px of distance
})Combine relative duration with min/max bounds:
new ScrollToSmooth('a', {
duration: 400,
durationRelative: true,
durationMin: 200, // never faster than 200ms
durationMax: 1500, // never slower than 1500ms
})Delegate to the browser's built-in scroll-behavior: smooth instead of using JavaScript animation:
new ScrollToSmooth('a', {
useNative: true,
})Or auto-detect: use native when supported, fall back to JS animation when not:
new ScrollToSmooth('a', {
useNative: 'auto',
})Note: Native scrolling doesn't support custom easing functions, but events and the scroll queue still work.
new ScrollToSmooth('a', {
onScrollStart({ startPosition, endPosition }) {
console.log(`Scrolling ${endPosition - startPosition}px`)
},
onScrollUpdate({ currentPosition, progress }) {
// progress is 0 → 1
progressBar.style.width = `${progress * 100}%`
},
onScrollEnd({ endPosition }) {
console.log('Arrived at', endPosition)
},
})All events bubble, so you can listen at any level of the DOM:
document.addEventListener('scrolltosmooth:start', (e) => {
console.log(e.detail.startPosition, '→', e.detail.endPosition)
})
document.addEventListener('scrolltosmooth:update', (e) => {
const { currentPosition, progress } = e.detail
// ...
})
document.addEventListener('scrolltosmooth:end', (e) => {
// animation finished
})Disable event dispatching for performance-sensitive scenarios:
new ScrollToSmooth('a', { dispatchEvents: false })During animation, ScrollToSmooth sets CSS custom properties on the scroll container:
--sts-scroll-y— current vertical scroll position (px)--sts-scroll-x— current horizontal position (requires Horizontal plugin)
Use them for reactive CSS without any JavaScript:
/* Parallax effect */
.parallax-layer {
transform: translateY(calc(var(--sts-scroll-y, 0) * -0.5px));
}
/* Fade in as you scroll */
.fade-section {
opacity: calc(var(--sts-scroll-y, 0) / 600);
}scroller.update({
duration: 800,
easing: easeOutQuint,
})Removes all event listeners and cleans up:
scroller.destroy()