Scroll direction and position as classes (is-scroll-up, is-scroll-down, is-scroll-top…), so CSS can hide a sticky header on the way down and bring it back on the way up. No dependencies, no jQuery.
The successor of jquery-scroll-direction-plugin.
npm i @phucbm/scroll-directionpnpm add @phucbm/scroll-directionimport {createScrollDirection} from '@phucbm/scroll-direction'
// or
import createScrollDirection from '@phucbm/scroll-direction'
createScrollDirection();<body> then always has one position class and, once the page has moved, one direction class:
| Class | When |
|---|---|
is-scroll-top |
within topOffset of the top (also on load at the top) |
is-scroll-middle |
between top and bottom |
is-scroll-bottom |
within bottomOffset of the bottom |
is-scroll-down |
last moved down by more than directionThreshold |
is-scroll-up |
last moved up by more than directionThreshold |
A sticky header that hides on the way down:
.site-header {
position: sticky;
top: 0;
transition: transform .3s ease;
}
.is-scroll-down:not(.is-scroll-top) .site-header {
transform: translateY(-100%);
}With Tailwind v4, as variants:
@custom-variant scroll-up (&:is(.is-scroll-up *));
@custom-variant scroll-down (&:is(.is-scroll-down *));
@custom-variant scroll-top (&:is(.is-scroll-top *));Use the header's height as "top", read on every update:
const header = document.querySelector('.site-header')!;
createScrollDirection({topOffset: () => header.getBoundingClientRect().height});Works with smooth-scroll libraries that move the window, such as Lenis. Libraries that fake scrolling with transforms on a wrapper (Locomotive Scroll v4) are not supported: they leave window.scrollY at 0.
| Option | Type | Default | Description |
|---|---|---|---|
target |
Element |
document.body |
Element that receives the classes |
prefix |
string |
'is-scroll-' |
Class prefix; 'scroll-' gives the jQuery plugin's class names |
topOffset |
number | () => number |
80 |
Within this many pixels of the top counts as top |
bottomOffset |
number | () => number |
100 |
Within this many pixels of the bottom counts as bottom |
directionThreshold |
number |
10 |
Pixels to travel the other way before the direction flips; measured from where the page last moved forward, so small wobbles never flip it |
| Event | Dispatched on | event.detail |
|---|---|---|
scrolldirection |
document |
{position: 'top' | 'middle' | 'bottom', direction: 'up' | 'down' | 'none', scrollY: number} |
Fired on load and whenever the position or the direction changes, not on every scroll.
document.addEventListener('scrolldirection', (e) => {
const {position, direction} = (e as CustomEvent).detail;
});Starts tracking (scroll and resize, one read per animation frame) and sets the classes for the current scroll position right away.
Instance methods:
| Method | Description |
|---|---|
getState() |
{position, direction, scrollY} now |
update() |
Read the scroll position now, e.g. after content changed the page height |
destroy() |
Remove the listeners and the classes |
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build the package
pnpm run build
# Type-check src and tests
pnpm typecheck
# Run tests in watch mode
pnpm run test:watchReleases are published to npm by .github/workflows/publish.yml with npm trusted publishing (OIDC): no npm token is stored anywhere.
Publish a GitHub release with a tag like v1.2.3. The workflow runs the tests, builds, sets package.json to that version, commits the build back to main and publishes to npm. The trusted publisher on npmjs.com names the workflow publish.yml.
MIT © phucbm