Programmable scroll physics engine for the web. Per-section friction, magnetic snap points, configurable mass and inertia.
~4KB | TypeScript | Zero dependencies | Pure computation
npm install @aumiqx/scrollimport { ScrollEngine } from "@aumiqx/scroll";
const engine = new ScrollEngine({
mass: 1.2,
friction: 0.95,
min: 0,
max: 3000,
zones: [
{ start: 0, end: 600, friction: 0.85 }, // slow hero
{ start: 600, end: 1200, friction: 0.97 }, // fast gallery
{ start: 1200, end: 1800, snap: true }, // snap pricing
],
magnets: [
{ position: 1500, strength: 0.4, range: 120 },
],
});
// On wheel events:
element.addEventListener("wheel", (e) => {
e.preventDefault();
engine.applyForce(e.deltaY * 0.3);
}, { passive: false });
// On every frame:
function tick() {
const state = engine.tick();
element.style.transform = `translateY(${-state.position}px)`;
requestAnimationFrame(tick);
}
tick();Every website on Earth uses identical scroll physics. Same mass, same friction, same inertia. The browser's scroll engine is a black box you can't modify.
This library replaces it with a physics engine where every parameter is yours.
| Feature | Native Scroll | Lenis | GSAP ScrollTrigger | @aumiqx/scroll |
|---|---|---|---|---|
| Custom friction | No | Partial | No | Per-section |
| Magnetic snap | CSS only | No | No | Configurable |
| Custom mass | No | No | No | Yes |
| Bounce walls | Platform-specific | No | No | Elastic |
| Pure computation | N/A | DOM-dependent | DOM-dependent | No DOM |
Create a scroll physics engine.
const engine = new ScrollEngine({
mass: 1.2, // scroll inertia (higher = more momentum)
friction: 0.95, // velocity decay per frame (0.80-0.99)
min: 0, // scroll bounds minimum
max: 3000, // scroll bounds maximum
zones: [], // per-section friction overrides
magnets: [], // magnetic snap points
walls: [], // custom bounce points
});Apply external force from wheel/touch events. Divided by mass internally.
element.addEventListener("wheel", (e) => {
engine.applyForce(e.deltaY * 0.3);
});Advance physics by one frame. Call on every requestAnimationFrame.
const state = engine.tick();
// state.position - current scroll position (px)
// state.velocity - current speed (px/frame)
// state.activeZone - which zone index is active (-1 if none)
// state.nearestMagnet - magnet index in range (null if none)
// state.isBouncing - whether hitting a wallJump to a position. Resets velocity.
engine.setPosition(1200); // teleport to pricing sectionUpdate physics at runtime.
// User enables "reduced motion"
engine.configure({ mass: 0.5, friction: 0.85 });Read current state.
interface ScrollZone {
start: number; // zone start (px)
end: number; // zone end (px)
friction?: number; // override friction (0.80-0.99)
snap?: boolean; // pull to center when slow
}Examples:
zones: [
{ start: 0, end: 600, friction: 0.82 }, // hero: heavy, cinematic
{ start: 600, end: 1200, friction: 0.975 }, // gallery: light, momentum
{ start: 1200, end: 1800, snap: true }, // pricing: magnetic center
{ start: 1800, end: 2400, friction: 0.80 }, // cta: maximum resistance
]interface ScrollMagnet {
position: number; // target position (px)
strength: number; // pull force (0-1)
range: number; // activation radius (px)
}Example:
magnets: [
{ position: 1500, strength: 0.4, range: 120 }, // pricing
{ position: 2100, strength: 0.5, range: 100 }, // cta
]- Storytelling landing pages - hero is slow, gallery is fast, CTA snaps
- WebGL camera control - mass and inertia create cinematic camera movement
- Reading experiences - long articles automatically slow scroll down
- E-commerce - product listings glide, checkout anchors
- Data visualization - scroll drives chart progression with zone-based pacing
- Scroll-driven games - scroll IS the game input with custom physics
Each frame:
- Force - wheel/touch input divided by mass
- Friction - velocity multiplied by zone-specific friction (0.80-0.99)
- Magnets - nearby magnets apply pull proportional to proximity
- Position - updated by velocity, clamped to bounds with bounce
velocity += force / mass
velocity *= zoneFriction
velocity += magnetPull
position += velocity
MIT