Skip to content

useScroll

ts
useScroll(target?: Element | Window): Service<ScrollProps>

Scroll position, movement and direction for one scroller. With no target it is the document element; useScroll(document.documentElement) is the window service.

Props

ts
interface ScrollProps {
  readonly x: number;
  readonly y: number;
  readonly deltaX: number;
  readonly deltaY: number;
  readonly maxX: number;
  readonly maxY: number;
  readonly progressX: number;
  readonly progressY: number;
  readonly directionX: -1 | 0 | 1;
  readonly directionY: -1 | 0 | 1;
  readonly isScrolling: boolean;
}

Nothing derivable is a field: lastX is x - deltaX, and changedX is deltaX !== 0.

Usage

js
import { 
Base
,
useScroll
} from '@studiometa/js-toolkit';
class
Header
extends
Base
{
static
config
= {
name
: 'Header' };
mounted
() {
return
useScroll
().
subscribe
(({
y
,
directionY
}) => {
this.
$el
.
classList
.
toggle
('is-hidden',
directionY
> 0 &&
y
> 100);
}); } }

A region rather than the page:

js
import { 
Base
,
useScroll
} from '@studiometa/js-toolkit';
class
Panel
extends
Base
{
static
config
= {
name
: 'Panel',
refs
: ['scroller'] };
mounted
() {
return
useScroll
(this.
$refs
.
scroller
).
subscribe
(({
progressY
}) => {
this.
$el
.
style
.
setProperty
('--progress',
String
(
progressY
));
}); } }

One read per frame

The service coalesces its scroll events into one read per frame, so a page scrolling at speed measures once per frame rather than once per event.

Extents are observed, not sampled once

The service watches the scroller and its element children with a ResizeObserver, plus a childList MutationObserver to keep that set correct: 1 + n observed boxes per scroller, lazy and released with the last subscriber.

That is what keeps maxY and progressY correct when content is added, removed or resized.

{ immediate: true } works here

A scroller has a current position between deliveries, so the first delivery is honoured. The first props of a run carry no movement: the deltas are zero and directionX/directionY are 0.

Mixin

js
class Header extends withScroll(Base) {
  scrolled({ y, directionY }) {}
}

withScroll defaults to the page-wide source. A region is withScroll(Base, { target: (instance) => instance.$refs.scroller }).

See also

MIT Licensed