How many dropped frames you can see

60fps is a budget, not a quality bar. This post runs an adaptive staircase on you and reports your own detection threshold in milliseconds.

A dropped frame is the screen holding a stale position for one extra refresh, then jumping to catch up. That's the whole mechanism. Jank is a run of those.

How many in a row you can actually perceive isn't a fixed number. It depends on your display, your eyes, the size of the moving element, and how fast it's travelling. Most performance writing skips past that and hands you a target instead: 60fps, 16.67ms, ship it.

You can measure your own threshold in about a minute. The method is a two-alternative forced choice driven by an adaptive staircase, which is how psychophysics measures any perceptual threshold.

Two lanes animate below. One of them hitches. You pick which one. Get it right twice in a row and the hitch gets shorter, get it wrong and it gets longer. The value oscillates around the point where you stop being able to tell, and the mean of those oscillations is the threshold.

Two lanes, one of them hitches

A two-alternative forced choice with an adaptive staircase. Get it right twice and it gets harder.

That number is in frames, and frames only mean something once you know how long yours are.

Frame budget

This display

...Hzrefresh rate
...msper frame budget
...msworst frame seen

Measured from rAF deltas while you read. The worst frame includes anything else your machine was doing.

At 60Hz a frame is 16.67ms. At 120Hz it's 8.33ms, so the same work that fit comfortably on the old display overruns on the new one. Code with 16 or 16.67 hardcoded in it is describing hardware a lot of your readers aren't using.

requestAnimationFrame won't tell you the rate. It just calls you when the next frame is due. To get the number, measure successive callback timestamps and take the median. The mean is worse here, because a single hitch during measurement drags it.

Evenness beats average

Frame rate is the number everyone reports and it's the wrong one on its own. Two animations updating on exactly half the available frames can feel completely different depending on how those updates are spaced.

Same average, different pacing

A
B

One of these updates on a regular cadence. Decide which looks worse before revealing.

Both of those update on 50% of frames. The top one spaces them evenly and reads as a slower but coherent animation. The bottom one bunches them, and the identical average now reads as broken.

This is why a locked 30fps usually feels better than an unlocked 60 that drops to 40 under load. Your eye tracks a moving object by predicting where it will be next. Even pacing keeps that prediction correct, just at a coarser resolution. Uneven pacing breaks it, and a broken prediction is what people are describing when they say something feels janky.

It also explains why frame rate averages hide the problems worth finding. An animation at 58fps average with one 90ms stall in it is worse than a steady 45, and the average reports the opposite.

What to do with the number

Whatever you landed on is a budget for a single stall, which is more useful than a target frame rate because a stall is the thing people actually notice. A hitch under your threshold is invisible. One over it isn't, regardless of what the average says.

The tighter constraint is consistency. A steady stream of 20ms frames is fine. One 20ms frame in a stream of 8ms frames is visible on a 120Hz display, because the jump is what registers, not the duration.

So when you profile, read the shape of the frame timeline rather than the average printed above it.