DOM Physics Engine
A simple, performant physics engine for DOM elements. Preserves DOM structure while enabling realistic physics simulation. Built with TypeScript.
đ Live Demos on GitHub Pages | đŚ npm
Why This Package?
Simple & Fast
- No complex nesting - Simple
Body and World classes
- Direct physics - No inheritance chains or recursive lookups
- Optimized collision detection - Simple O(n²) for small counts, efficient for most use cases
- Minimal overhead - Only manipulates CSS
transform properties
What Changed from Complex Version?
The previous version had advanced features like:
- World extending Body (nesting)
- Physics inheritance with recursive lookups
- SpatialHash optimization (removed - not needed for typical use cases)
- Complex coordinate transformations
Why we simplified:
- Performance - Recursive lookups (
getEffectiveGravity(), getWorldPosition()) were called every frame, causing O(n) complexity
- Simplicity - Most use cases don't need nesting - a simple World with Bodies is enough
- Reliability - Fewer moving parts = fewer bugs
- Maintainability - Easier to understand and modify
Result: The simplified version matches the original demo's performance exactly while being much easier to use and understand.
Features
â
DOM Structure Preserved - Never modifies DOM hierarchy, only manipulates transforms
â
Simple API - Just World and Body classes
â
Framework Agnostic - Works with vanilla JS, React, Vue, etc.
â
TypeScript - Full type safety and IntelliSense support
â
Performant - Optimized for 60fps with many bodies
Installation
npm install dom-physics
Quick Start
import { World, Body } from 'dom-physics';
const container = document.getElementById('world');
const world = new World(container, {
gravity: 400,
friction: 0.97,
restitution: 0.5
});
const element = document.querySelector('.my-element');
const body = new Body(element, world, {
mass: 1,
radius: 15,
restitution: 0.8
});
world.registerBody(body);
world.start();
Demos
đ Try all demos live on GitHub Pages
Main Demo (Published Demo)
Text Demo (demo-text.html) - Interactive text that responds to mouse movement. This is the main published demo showcasing the package.
Additional Demos
- Squares Demo (
demo-squares.html) - Click to add squares, hover to push them around. Demonstrates collision detection and bounds.
- Bouncing Balls (
demo-bouncing.html) - Colorful balls bouncing in a circular container. Shows high restitution physics.
- Stack Demo (
demo-stack.html) - Build towers by clicking. Watch blocks stack and balance with realistic physics.
Running Demos Locally
Option 1: Using npm (if installed as package)
npm install dom-physics
cd node_modules/dom-physics
npm run demo:package
Option 2: From Source (if developing)
git clone https://github.com/kai4avaya/dom-physics.git
cd dom-physics
npm install
npm run build
npm run demo:package
Option 3: Original Inline Demo
npm run demo
API Reference
World
class World {
constructor(container: HTMLElement, config?: WorldConfig)
start(): void
stop(): void
registerBody(body: Body): void
unregisterBody(body: Body): void
container: HTMLElement
bodies: Body[]
gravity: number
friction: number
restitution: number
timeStep: number
bounds: { x: number; y: number; width: number; height: number }
}
Body
class Body {
constructor(
element: HTMLElement,
world: World,
config?: BodyConfig
)
applyForce(fx: number, fy: number): void
getWorldPosition(): { x: number; y: number }
render(): void
element: HTMLElement
world: World
x: number
y: number
mass: number
radius: number
restitution: number | null
friction: number | null
isStatic: boolean
enabled: boolean
}
Configuration
WorldConfig
interface WorldConfig {
gravity?: number;
friction?: number;
restitution?: number;
bounds?: {
x: number;
y: number;
width: number;
height: number;
};
timeStep?: number;
}
BodyConfig
interface BodyConfig {
mass?: number;
radius?: number;
restitution?: number | null;
friction?: number | null;
isStatic?: boolean;
}
Examples
Basic Usage
const world = new World(container, {
gravity: 400,
friction: 0.97,
restitution: 0.5
});
const body = new Body(element, world, {
mass: 1,
radius: 15
});
world.registerBody(body);
world.start();
Mouse Interaction
container.addEventListener('mousemove', (e) => {
const rect = container.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
world.bodies.forEach(body => {
const pos = body.getWorldPosition();
const dx = pos.x - mx;
const dy = pos.y - my;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100 && dist > 0) {
const force = (100 - dist) * 2;
body.applyForce(
(dx / dist) * force,
(dy / dist) * force
);
}
});
});
Custom Physics Properties
const body1 = new Body(element1, world);
const body2 = new Body(element2, world, {
restitution: 0.9
});
const body3 = new Body(element3, world, {
friction: 0.95
});
Principles
- DOM Structure Preserved - Never modifies DOM hierarchy
- Transform Only - Only manipulates
transform CSS property
- World Space Simulation - Bodies simulate in world space, render relative to DOM
- Simple & Fast - No unnecessary complexity
Testing
npm test
npm run test:run
npm run test:coverage
License
MIT