Scroll
Smooth scrolling between pages, to anchors and to the top.
Import it from pagefold/scroll (about 2.1 KB minified and compressed, shared code included).
import PagefoldScroll from 'pagefold/scroll'
new Pagefold({
plugins: [
new PagefoldScroll({
duration: 800,
offset: () => document.querySelector('header').offsetHeight,
animateScroll: { betweenPages: false }, // jump between pages, glide to anchors
}),
],
})
What it animates
| Kind | Example |
|---|---|
betweenPages | Going to another page: scrolls to the top, or to the anchor if the URL has one |
samePageWithHash | A link to #section on the same page |
samePage | A link to the page you are already on, which scrolls to the top |
Reset or restore
On every visit the page goes back to the top, as a browser would. On back and forward visits it goes to where you were when you left that page. Pagefold remembers the position of each page you leave, so this also works when animateHistoryBrowsing is on, which makes Pagefold, and not the browser, responsible for scrolling.
shouldResetScrollPosition changes this for links. A custom “back” link would normally scroll the new page to the top, while visitors expect it to behave like the back button:
new PagefoldScroll({
shouldResetScrollPosition: (link) => !link.matches('.backlink'),
})
Scroll containers
An element with its own scrollbar, such as an overflowing table or a sidebar, is not part of the window’s scroll. Mark the ones that matter and Pagefold resets them on a visit and restores them on back:
<aside data-pagefold-scroll-container>...</aside>
Or choose them with the option: scrollContainers: '.sidebar, .results'. They are remembered by their order in the page, so a container that is replaced by a visit starts at the top anyway, and one that stays outside the containers is the one that benefits.
Marking the target
:target stops working on sites that add their own history entries. With markScrollTarget: true the element a hash points to gets the attribute data-pagefold-scroll-target, which you can style instead:
[data-pagefold-scroll-target] {
outline: 2px solid var(--accent);
}
Hooks and the instance
scroll:start and scroll:end fire around an animated scroll. The plugin also adds pagefold.scrollTo(position, animate), the same as calling it on the plugin.
pagefold.hooks.on('scroll:start', () => document.body.classList.add('is-scrolling'))
pagefold.hooks.on('scroll:end', () => document.body.classList.remove('is-scrolling'))
await pagefold.scrollTo(2000, true)
Stopping
The animation stops as soon as the user takes over (wheel, touch, key press or mouse down) or a new visit starts.
Scrolling with your own function
To scroll with GSAP, or anything else, give the plugin a scrollFunction. It gets the position to reach and a context, and must call ctx.done() when it finishes. Return a function to stop it: Pagefold calls it when the user takes over or a new visit starts.
import { gsap } from 'gsap'
import { ScrollToPlugin } from 'gsap/ScrollToPlugin'
gsap.registerPlugin(ScrollToPlugin)
new PagefoldScroll({
scrollFunction: (to, { duration, done }) => {
const tween = gsap.to(window, {
scrollTo: { y: to.top, x: to.left },
duration: duration / 1000,
ease: 'power2.inOut',
onComplete: done,
})
return () => tween.kill()
},
})
Jumps (animateScroll: false, reduced motion, or scrollTo(y, false)) do not use it.
Sideways
A page or container that scrolls horizontally can use offset: { left: 40 }. Without left, scrolling to an element never moves the page sideways.
From code
const scroll = new PagefoldScroll()
await scroll.scrollTo(0) // animated
await scroll.scrollTo(document.querySelector('#pricing'))
await scroll.scrollTo(300, false) // jump
It replaces the default behavior of the scroll:top and scroll:anchor hooks. Without animateHistoryBrowsing, back and forward visits keep the browser’s own scroll restoration.
Common problems
- The page jumps instead of gliding. Between pages the old content is usually hidden while it changes, so many sites turn
betweenPagesoff. CheckanimateScroll. - A sticky header covers the target. Set
offsetto the header’s height, as in the example above. - Back and forward do not animate. They keep the browser’s own scroll restoration, on purpose.
Options
| Option | Type | Default | What it does |
|---|---|---|---|
animateScroll | boolean | object | true | true animates every kind. An object picks which: betweenPages, samePageWithHash, samePage. |
duration | number | 600 | Duration in milliseconds. |
easing | (t: number) => number | easeInOutCubic | Maps progress from 0 to 1 onto distance from 0 to 1. |
offset | number | { top, left } | (el) => number | { top, left } | 0 | Space to leave above the target, for example for a fixed header. { left } does the same sideways, for pages and containers that scroll horizontally. |
scrollFunction | (to, ctx) => void | (() => void) | undefined | Do the scrolling yourself, with GSAP for example. Call ctx.done() when it ends. Jumps and reduced motion do not use it. |
scrollContainers | string | '[data-pagefold-scroll-container]' | Elements other than the window whose scroll position is reset on a visit and restored on back. |
shouldResetScrollPosition | (link) => boolean | () => true | Return false to restore the position of the page a link goes to, instead of going to the top. |
markScrollTarget | boolean | false | Mark the element a hash points to with data-pagefold-scroll-target, to style it. |
getAnchorElement | (hash) => Element | null | undefined | Find the element a hash points to. Default: the element with that id, or a[name]. |
doScrollingRightAway | boolean | false | Go to the top when the visit starts, not after the new content is in. |
respectReducedMotion | boolean | true | Jump instead of animating when the user prefers reduced motion. |