Make Your Processing Sketches Frame-Rate Independent with millis()
Processing sketches that move objects by fixed pixels per frame run at different speeds on different displays. Compute delta time from millis() to make motion frame-rate independent.
29 Jan 2026, 05:28 UTC

The Same Sketch, Two Different Speeds
If you move a shape by a fixed number of pixels inside draw(), you are not controlling speed—you are controlling distance per frame. On a display that refreshes at 30 Hz, a sketch that advances a circle by 4 pixels each frame crosses the canvas in one amount of time. On a 144 Hz monitor, the same code crosses it in a fraction of that time. The motion is tied to the frame rate, not to wall-clock time.
The fix is straightforward: compute how much real time passed since the last frame, then move the object by velocity * elapsedSeconds. Processing (Java mode) gives you millis() for this, but it does not provide a built-in deltaTime variable, so you must compute the delta yourself.
Why Fixed Per-Frame Increments Drift
Processing calls draw() in a loop. When you write x = x + 4;, you are saying "move 4 pixels per frame." If the loop runs 30 times per second, the object travels 120 pixels per second. If the loop runs 120 times per second, it travels 480 pixels per second. The frame rate is not stable: frameRate(60) sets a target, not a guarantee. Garbage collection, window drags, and operating-system scheduling all introduce variation. Fixed increments amplify that variation into visible speed changes.
Computing Delta Time from millis()
millis() returns an int containing the number of milliseconds since the sketch started. To get frame-rate independence, store the previous value, subtract it from the current value, and convert milliseconds to seconds:
int lastMillis;
void setup() {
size(400, 300);
lastMillis = millis();
}
void draw() {
int currentMillis = millis();
float dt = (currentMillis - lastMillis) / 1000.0;
lastMillis = currentMillis;
// advance positions by velocity * dt
x += vx * dt;
y += vy * dt;
}
Velocities are now expressed in pixels per second. A vx of 120 means 120 pixels per second, regardless of whether draw() runs 30 or 120 times per second.
Worked Example: A Bouncing Shape
Here is a complete sketch that keeps a circle inside the window using time-based motion:
float x, y;
float vx = 120; // pixels per second
float vy = -80; // pixels per second
float r = 20;
int lastMillis;
void setup() {
size(400, 300);
x = width / 2.0;
y = height / 2.0;
lastMillis = millis();
}
void draw() {
int now = millis();
float dt = (now - lastMillis) / 1000.0;
lastMillis = now;
// Clamp to absorb stalls (see below)
if (dt > 0.1) dt = 0.1;
x += vx * dt;
y += vy * dt;
// Bounce off edges
if (x - r < 0 || x + r > width) {
vx = -vx;
x = constrain(x, r, width - r);
}
if (y - r < 0 || y + r > height) {
vy = -vy;
y = constrain(y, r, height - r);
}
background(0);
fill(255);
ellipse(x, y, r * 2, r * 2);
}
Because the position update uses vx * dt, switching frameRate(30) to frameRate(120) should not change how long the circle takes to cross the canvas. The crossing time is now determined by the velocity and the distance, not by how often the display refreshes.
The int Overflow Caveat and the Delta Clamp
millis() returns a 32-bit integer. After roughly 24.9 days of continuous uptime, it wraps around to a negative value. If you subtract a large positive lastMillis from a small negative now, the result is a huge delta. A naive sketch would teleport the shape across the screen.
The practical mitigation is to clamp dt to a maximum of about 50 to 100 milliseconds. This absorbs stalls from window drags, garbage-collection pauses, or a backgrounded window. It also prevents the overflow case from producing a visible jump: even if the subtraction yields an enormous value, the clamp caps the movement for that frame.
This is an integer-overflow mitigation, not a Processing defect. It is a known property of 32-bit millisecond counters.
What This Does Not Solve
Time-based motion fixes speed consistency. It does not fix responsiveness. If a sketch blocks on network or file I/O, the frame rate drops and the animation stutters, but the object still moves at the correct speed when frames do render. For event-driven sketches that only need to update on input, noLoop() and redraw() are the right tools. For blocking operations, thread() keeps the animation responsive. These are separate problems from delta-time calculation; do not blend them into the same fix.
How to Verify the Result
You can check the behavior without external tools:
- Run the sketch with
frameRate(30)and time how long the shape takes to cross the canvas. Change the target toframeRate(120)and time it again. The durations should match within a frame or two. - Add
println(dt);insidedraw(). The values should stay near1000 / targetFrameRateconverted to seconds, and they should never exceed the clamp during normal interaction. - Drag or resize the window to force a stall. When drawing resumes, the shape should not jump; the clamp should have absorbed the long frame.
- Watch the
frameRatereadout in the Processing console. Low FPS should correlate with largerdtvalues, not with faster motion.
If the shape still moves faster on a high-refresh display, check that you are not mixing a fixed per-frame increment anywhere else in the update path. Every positional change should be multiplied by dt.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.