@openuji/scrollcraft
v0.1.1
Published
**Scrolling Orchestrator** — A small DOM scroll engine for cases where native scrolling is too limiting. It lets you plug in your own inputs, easing, and domain semantics (bounded, unbounded, circular, or hybrid) while still playing nicely with the browse
Downloads
197
Readme
@openuji/scrollcraft
Scrolling Orchestrator — A small DOM scroll engine for cases where native scrolling is too limiting. It lets you plug in your own inputs, easing, and domain semantics (bounded, unbounded, circular, or hybrid) while still playing nicely with the browser.
Why
- Programmable domains: bounded, end-unbounded, all-unbounded, circular, or circular-end-unbounded semantics.
- Decoupled pieces: driver (how to read/write), inputs (wheel/touch), scheduler (raf), and animator (e.g., exponential).
- Middleware-friendly: compose plugins such as sessionStorage persistence without wiring everything by hand.
- Host vs engine authority: opt into native
scrollTo({behavior: "smooth"})for programmatic scrolls while keeping engine control for user inputs.
Installation
pnpm add @openuji/scrollcraftQuick start (DOM window)
import { defaultScrollEngine } from "@openuji/scrollcraft";
const engine = defaultScrollEngine(); // window driver, wheel + touch inputs
engine.init();
// Programmatic scrolls
engine.scrollTo(480); // animated
engine.scrollTo(0, true); // immediate jump
// Cleanup when you tear down the page/app
engine.destroy();defaultScrollEngine wires a DOM driver on the window, wheel/touch inputs on document.body, a requestAnimationFrame scheduler, exponential animator, and sessionStorage restoration middleware (restoreMode: "immediate").
Custom build
For more control, use the building blocks directly:
import {
createDOMDriver,
createRafScheduler,
createEngine,
createGesturePort,
createCommandPort,
createDomainRuntime,
wheelInput,
touchInput,
expAnimator,
} from "@openuji/scrollcraft";
const target = document.querySelector<HTMLElement>("#scroller")!;
// Create core components
const driver = createDOMDriver(target, "block"); // or "inline"
const scheduler = createRafScheduler();
const domain = createDomainRuntime(driver.limit);
// Build the engine
const engine = createEngine(driver, scheduler, domain);
// Wire up input handling (wheel + touch → impulses)
const gestures = createGesturePort({
inputs: [
wheelInput({ element: target, multiplier: 0.8 }),
touchInput({ element: target, multiplier: 2 }),
],
engine,
animator: expAnimator(0.1),
});
// Wire up programmatic scroll commands
const command = createCommandPort({ engine, animator: expAnimator(0.15) });
// Start everything
gestures.init();
command.scrollTo(480); // animated scrollDomains
The DOM driver defaults to a bounded domain [0, scrollSize - clientSize]. To change semantics, expose a domain() method on your driver that returns:
boundedwithmin/maxend-unboundedorall-unboundedcircular-unbounded(wraps both ends) orcircular-end-unbounded(bounded start, circular end) withperiod
For a ready-made loop, use circularScrollEngine() which wraps the DOM driver in a circular-end-unbounded domain.
API highlights
engine.init()/engine.destroy()engine.scrollTo(value, immediate?)engine.applyImpulse(delta)(from inputs)engine.seedInitialPosition(pos)(beforeinitto avoid jumps)engine.getDomain()/engine.getPosition()engine.schedule(cb)to piggyback on the internal scheduler
Helpers:
createDOMDriver(target, axis)to read/write scroll positions.wheelInputandtouchInputto convert events into impulses.createRafSchedulerfor animation frames.expAnimator(lerp)for exponential easing with automatic settle detection.sessionStoragePersistencemiddleware to save/restore scroll position; options include custom key,restoreMode(immediate|afterLayout), layout timeout, and opt-in hooks.
Development & testing
- Build:
pnpm --filter @openuji/scrollcraft build - Watch:
pnpm --filter @openuji/scrollcraft dev - Lint:
pnpm --filter @openuji/scrollcraft lint - Type-check:
pnpm --filter @openuji/scrollcraft check-types - Tests (Playwright):
pnpm --filter @openuji/scrollcraft test— ensure Playwright browsers are installed (pnpm exec playwright install).
