Aumiqx Technologies
AUM
Open menu
Lab 02source on GitHub

Every site scrolls the same. Yours doesn't have to.

A small physics engine for scroll. Give each section its own friction, add magnets that pull readers toward what matters, and move anything with the position it returns. Pure math, no DOM, zero dependencies.

size
~4KB
size
dependencies
0
dependencies
friction zones
per-section
friction zones
@aumiqx/scroll

01 · The problem

Same page. Different physics.

Scroll inside each box, or fling both at once. The left one uses a single friction value, like every browser. The right one gives each section its own feel and a magnet on pricing.

Browser scroll

friction 0.95 everywhere

hero
features
pricing
social
cta
vel
0.0

scroll here

@aumiqx/scroll

per-section friction + a magnet

hero
features
pricing
social
cta
vel
0.0

scroll here

same force, same moment. watch where each one lands.

02 · Physics bench

Fling it. Then change the laws.

One puck, the real engine. Drag across the track or hit fling, then tune mass, friction and the magnet. The chart plots velocity every frame, so you can see exactly how each setting decays.
drag to flingmagnet

Velocity, last 4 seconds

0.0 px/f

Presets

Last peak

0.0

Settled in

0f

03 · How it works

Four steps. Every frame.

The engine runs a tiny loop at 60fps and hands you a number. What moves is up to you.
  1. F01

    Force

    A wheel or touch event pushes the scroll. Heavier mass, smaller push.

  2. f02

    Friction

    Each zone decays velocity at its own rate, every frame.

  3. M03

    Magnets

    Magnets in range pull the position toward their target.

  4. P04

    Position

    You get one number back. Move whatever you like with it.

velocity += force / mass·velocity *= zoneFriction·position += velocity

04 · Playground

Five sections. Five kinds of scroll.

A mock landing page where every section has its own physics. Follow the steps, and drag the mass slider to change how the whole thing feels.
  1. 1Scroll slowly and feel the hero resist
  2. 2Keep going, features let you glide
  3. 3Approach pricing, the magnet grabs you
  4. 4Set mass to 4 and flick hard
hero

Scroll feels heavy here. First impressions deserve a slower pace, so the reader sinks in instead of flying past.

friction 0.82 · heavy

features

Now it feels light. One flick and momentum carries you, like flipping through cards in a gallery.

friction 0.975 · glide

pricing

This section has a magnet. Try to scroll past it and the engine pulls you back to the centre.

snap + magnet · hard to skip

social proof

A comfortable reading pace, tuned for testimonials and case studies.

friction 0.93 · reading pace

cta

Maximum resistance plus a magnet. The call to action holds you in place.

friction 0.80 + magnet · anchored

velocity
0.0 px/f

scroll inside to feel each section

Mass

1.20.3 to 5

Higher mass means more momentum and a slower, more cinematic response.

Active zone

hero

friction 0.82 · heavy

size

~4KB

deps

0

DOM

not needed

per tick

pure math

05 · The code

About twenty lines. Then it's yours.

Create an engine, feed it wheel events, call tick() every frame. It gives you a position, and you decide what moves.
scroll.ts
import { ScrollEngine } from "./scroll" // from github.com/aumiqx/scroll

const engine = new ScrollEngine({
  mass: 1.2,
  friction: 0.93,
  zones: [
    { start: 0, end: 500, friction: 0.85 },
    { start: 500, end: 1000, friction: 0.97 },
    { start: 1000, end: 1500, snap: true },
  ],
  magnets: [{ position: 750, strength: 0.4, range: 120 }],
})

element.addEventListener("wheel", (e) => {
  engine.applyForce(e.deltaY * 0.3)
})

function tick() {
  const { position } = engine.tick()
  el.style.transform = `translateY(${-position}px)`
  requestAnimationFrame(tick)
}
tick()
applyForce(delta)

Feed input from any event.

tick()

Advance the physics by one frame.

configure(opts)

Change settings at runtime.

Install

Not on npm yet. Clone github.com/aumiqx/scroll and import the engine directly. It has no dependencies.

06 · What you could build

It outputs a number. Anything can follow it.

01

Storytelling landing pages

A slow, dramatic hero. A fast feature gallery. A CTA that holds you.

02

Cinematic WebGL

Drive a 3D camera with scroll. Mass gives you Steadicam movement.

03

Long-form reading

Raise friction in dense sections so readers slow down without noticing.

04

Product listings

Physical momentum through a catalogue, with a magnet on the checkout button.

05

Scroll-driven data stories

Zones map to datasets. Magnets land on the moments that matter.

06

Scroll games

Runners and puzzles controlled entirely by scroll. The engine becomes the game loop.

07 · Reference

Every option. At a glance.

Configuration

OptionTypeDefaultWhat it does
massnumber1Scroll inertia. Higher means more momentum and a slower response.
frictionnumber0.95Base velocity decay per frame. 0.80 stops fast, 0.99 glides.
min / maxnumber0 / InfinityScroll bounds. Hitting a bound bounces.
zonesScrollZone[][]Per-section friction overrides and snap behaviour.
magnetsScrollMagnet[][]Points that pull the scroll toward them when in range.
wallsScrollWall[][]Custom bounce points with configurable elasticity.

API

applyForce(delta)
Apply external force from wheel or touch. Divided by mass.
tick()
Advance one frame. Returns position, velocity, zone, magnet and bounce state.
setPosition(pos)
Jump to a position and reset velocity.
configure(opts)
Update mass, friction, zones and magnets at runtime.
.position
Current scroll position.
.velocity
Current velocity in px per frame.
.state
Full state snapshot.

ScrollZone

{
  start: number,
  end: number,
  friction?: number, // 0.80 to 0.99
  snap?: boolean     // pull to centre
}

ScrollMagnet

{
  position: number,  // target px
  strength: number,  // 0 to 1
  range: number      // activation radius
}

More from the lab.

All labs