A lot of the browser game questions that land on here come down to physics behaving one way on the dev machine and another way on someone else's, and the engine is almost never the reason. This is the version of the explanation I wish someone had given me early, and it applies the same whether you are on Rapier, Cannon-es or Havok.
The Fixed Step Is The Whole Trick
A physics engine advances the world one step at a time. Each step it applies forces, finds contacts, resolves them, and writes new positions and velocities. If you hand it the real elapsed frame time, that step is a different size on every machine, and the numerical integration that turns force into velocity into position produces a different answer each run. Determinism goes, and replays, lockstep multiplayer and reproducible bug reports go with it.
Keep the step fixed, usually at 1/60th of a second, and decouple it from rendering. Accumulate real elapsed time in a buffer, run as many whole physics steps as fit in it, and leave the remainder for the next frame. When the renderer is faster than the physics rate, interpolate between the last two physics states so the motion still looks smooth. A slow frame then runs several small steps to catch up instead of one giant one, which is exactly what stops a fast projectile from tunneling straight through a thin wall.
Body Type Is The Decision People Get Wrong
Every body is dynamic, kinematic or static. Dynamic bodies are moved by forces and pushed by collisions. Kinematic bodies are moved by your code, shove dynamic bodies out of the way and ignore forces entirely. Static bodies never move and cost the least.
Picking wrong is the most common setup mistake I see. A moving platform written as a dynamic body drifts the moment the player lands on it. A player character written as dynamic gets shoved around by every crate it brushes. Most platforms want to be kinematic, most scenery wants to be static, and dynamic belongs to the objects you actually want the simulation to decide for you.
The rest is a handful of properties: mass, moment of inertia, linear and angular velocity, restitution for bounce, friction for grip. Forces apply continuously, like gravity every step. Impulses are instantaneous, like the kick of a jump. Mix those two up and you get a character that either floats or launches into orbit.
Collision Shapes Are Approximations On Purpose
The engine does not use your visual mesh. A 10,000 polygon character is far too expensive to test against every other body every step, so you swap it for a capsule, a box or a convex hull. That is a feature, not a compromise. A sphere against a sphere is one distance comparison. A convex hull pair needs GJK. A triangle mesh is the slowest of the lot, and it belongs on static terrain and almost nowhere else.
What To Reach For
The engines split into pure JavaScript and WebAssembly. Cannon-es is the readable JS option from the pmndrs community, comfortable to roughly 500 to 1,000 active bodies at 60 fps, with use-cannon for React. Rapier is Rust compiled to WASM, SIMD accelerated and several times faster than its 2024 releases, with joints, character controllers and ray casting. Havok's free WebAssembly build ships as the default plugin in Babylon.js 6 and later and is dramatically faster than the old Ammo.js path.
The part that matters more than the ranking is that a physics engine computes positions and rotations and draws nothing at all. Any of them pairs with any renderer, whether that is Three.js, Babylon.js, PixiJS or raw WebGL, and your code is the bridge that copies transforms across once per frame. The full web physics engine comparison covers the 2D options and the older ports too.
The Takeaway
Get the time step right before touching anything else. Fix the step, accumulate the remainder, interpolate for rendering, then choose body types deliberately and keep collision shapes simple. Do those three and the engine you pick becomes a performance decision instead of a correctness one.