Physical Simulation with JavaScript in the HTML5 Canvas

A demo animation of balls and springs showing realistic behavior

Don Cross
Don Cross
Nov 27 · 6 min read
Photo by Noah Silliman on Unsplash

In this article I will show you an example of how to use some basic physics ideas to create a simple animation with realistic-looking behavior. We will use JavaScript to render the animation inside an HTML5 canvas.

Before we dive into the code, take a look at the simulation running live in your browser. Try clicking and dragging near one of the green particles with your mouse.

The complete code for this browser-based animation is available in this GitHub repo. There are only two files, index.html and chain.js, and they have no external dependencies. You can download them to your own computer, tinker with the the code, and see the results in your browser.

The Physical Model

In this sample program, the simulated world is two-dimensional. There are three kinds of entities in this world: balls, anchors, and springs.

A ball is a particle that has a mass m, position (x, y), and velocity (vx, vy). The mass is constant, but the position and velocity vectors are updated during each animation frame based on the total forces acting on the ball. The balls are depicted as green circles. There is also a gravitational acceleration g = 9.8 m/s² that pulls the balls toward the bottom of the screen.

An anchor is a special kind of ball that does not move. (Actually, you can move it around with the mouse, just like you can with any other ball. But other than that, an anchor stays put.) There is only one anchor in the simulation as presented, but you can change that by editing the code in chain.js. Anchors look like red squares.

A spring is an elastic line segment that attaches to two balls at its endpoints. Every spring is connected to exactly two balls, but a ball may be connected to any number of springs. Each spring has two properties: a rest length and a spring constant. These are both positive real numbers that define how much force the spring applies to the two balls connected to its ends.

The rest length L is the length of a spring when it is neither stretched nor compressed. At this length, it will impart zero force to the balls it is attached to. L is expressed in meters.

The spring constant K tells how “strong” the spring is, meaning how much it resists being stretched or compressed away from its rest length. K is expressed in units of newtons per meter (N/m).

Now that we know the players, let’s explore how the simulator calculates their behavior in real time.

Animation in a Canvas

The animation itself occurs by drawing one animation frame at a time via the following steps:

  • Perform physics calculations to update the model state.

Calculating the Forces

In chain.js you will find a class Simulation. Inside it is a member function Update. Its parameter dt, expressed in seconds, represents a small increment of time by which the simulation’s state is to be updated. Before reading the code in the Update function, it will help to have a mental picture of the forces acting on a ball.

Spring and weight forces acting on the ball B1.

Every spring acts on both of the balls connected to it. Here, spring Sa acts on balls B1 and B2 with equal and opposite force Fa. Likewise, Sb acts on B1 and B3 with equal and opposite force Fb.

Let’s assume that the spring Sa at the depicted moment is stretched longer than its rest length. This is why the arrow for the force Fa is pointing away from the ball B1. At the same time, the same force Fa is pulling B2 in the opposite direction, toward B1.

On the other hand, suppose spring Sb is compressed shorter than its rest length. Therefore, Sb is trying to push B1 and B3 apart. That is why the force vector Fb has an arrow pointing toward B1.

There is also the ball’s weight mg pulling it downward, where m is the ball’s mass in kilograms, and g = 9.8 m/s² is acceleration due to gravity.

The function Update adds up all these contributory force vectors for every ball.

Update() calculates forces and updates ball positions and velocities accordingly.

The Update function calls the AddForce function inside the Spring class to add the forces of each spring in the simulation onto their connected balls. Here is that function:

AddForce() calculates the force of one spring on two balls.

Each spring has a force calculated by multiplying the spring constant by the the difference between the spring’s current length and its rest length. The force acts along a straight line between the two balls it connects.

Updating Position and Velocity Vectors

After tallying the forces acting on all the balls, Update goes back and updates the positions and velocities for each ball based on those forces. The kinetic behavior of the balls is governed by the familiar Newtonian physics equation F=ma. This says that the total force vector F on a mass m causes the mass to accelerate with a vector a.

The simulation proceeds in discrete time steps Δt. In physics, acceleration is defined as the instantaneous rate of change of velocity with respect to time, or a=dv/dt in differential calculus notation. We approximate acceleration as a ratio of finite changes avt. Putting this approximation together with F=ma, we can solve for the change in a ball’s velocity vector as Δv=(F/mt. We divide the total force F acting on the ball in the x- and y-directions by the ball’s mass m, and multiply by the time increment Δt, to get the change in velocity in the x- and y-directions.

A Glitch in the Matrix

But this approximation for Δv causes a problem in the simulation. If you use that naive formula, you will discover that the simulation becomes unstable. Unlike the infinitely small time increment dt, a finite increment Δt causes slight imperfections in the total energy of the system (kinetic energy plus potential energy). This can cause the balls to move faster and faster over time and explode off the screen.

To temper the behavior, we multiply all the speed vectors by a friction factor that is slightly smaller than 1. Over time this dampens the movement of the balls and keeps the simulation stable. You can adjust this friction behavior by editing the value of the constant FrictionHalfLifeSeconds in the JavaScript code. The smaller you make this half-life value, the more friction there will be. Make it small enough and everything looks like it’s moving through honey. Make the half-life constant too large, and you will see the explosion problem.

The Update function also updates the position of each ball by multiplying the velocity vector by the time Δt to figure out how much the position of the ball changed.

The Animation

In addition to the friction trick mentioned above, the simulation stability is enhanced by performing 1000 incremental simulation updates for each displayed animation frame. This makes the Δt value 1/1000 the size of the time interval between screen updates. A smaller value of Δt makes the simulation more accurate.

This amount of calculation per frame may sound excessive, but JavaScript is amazingly efficient these days. Modern browsers compile JavaScript into native machine code that is surprisingly optimized. It’s nowhere near the performance of C or Fortran for number crunching, but it’s nothing to sneeze at.

In practice, my performance testing with this code revealed the actual bottleneck is not in the physics calculations at all. The most expensive part of the code is erasing the canvas each time the Render function is called:

context.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);

So if you are itching to make this code faster, I would recommend starting there!

I hope you enjoy tinkering with this code. I’m certain there are numerical simulation gurus out there who can tell me ways it can be improved, especially in better approximating the physics behavior with finite time increments. I’d love to hear from you! Feel free to leave comments so we can all learn.

Resources

  • ChainSim — live browser-based demo of the algorithm described here.

JavaScript in Plain English

Learn the web's most important programming language.

Don Cross

Written by

Don Cross

Husband, computer programmer, yoga teacher, cat servant. https://github.com/cosinekitty

JavaScript in Plain English

Learn the web's most important programming language.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade