Shagun Mishra
Hand scroll Voice scroll Touch scroll Bench

Web Development
Brand Vantage

Shagun Mishra

Sixteen milliseconds — how a browser draws a frame

Scroll

01 — The budget

Sixty frames a second, or you feel it

A 60 Hz display asks the browser a question every 16.7 milliseconds: what should I show now? Everything else on this page is a consequence of that deadline.

Miss it once and nobody notices. Miss it regularly and the page acquires that particular cheapness — scrolling that lags a finger behind, animation that stutters on the same frame every time. Users never diagnose it. They just decide the site feels bad.

Inside those 16.7 ms the browser runs five stages in a fixed order. Load any one of them too heavily and the frame is late.

Where the 16.7 milliseconds go

A rough shape, not a measurement — the split shifts with what your page does.

Blow the budget on purpose

This burns real CPU inside the animation callback, exactly where your own slow code would sit. Push it past 16 and watch the counter fall.

Frame time — · — fps · budget —

—frames / sec now
—page weight
0dependencies
—sim grid
—your GPU

02 — Layout

The cheapest work is the work you don't force

Layout is the browser working out where every box goes. It is happy to do this once per frame, in a batch, at a moment of its choosing.

What it cannot do is answer a question about geometry while geometry is dirty. Ask for offsetHeight after you have changed a style and the browser must stop and lay out the document there and then. Do that in a loop and you have layout thrashing — the same work, hundreds of times.

The demo below runs the identical 600 operations twice. The only difference is the order.

Same work, two orders

600 read/write pairs per pass

03 — Paint

Turning shapes into pixels

Paint is rasterisation: taking geometry the browser now understands and filling in actual coloured pixels. Its cost scales with area, not with how many elements you have — one full-screen gradient can cost more than a thousand small icons.

The field below is a raster fill you control directly: every dot is pixels written into a canvas, so the count is the cost.

Move your cursor across the field

0 particles · — fps · — px filled / frame


  

04 — Composite

The GPU's real job: moving rectangles

Once regions are painted they become layers, and the compositor's job is to stack and transform them. This is the cheap stage — a layer can be moved, scaled or faded without repainting a single pixel.

That is the whole reason transform and opacity animate smoothly while top and height do not. One pair is a compositor instruction; the other invalidates layout and paint.

The background of this page never touches layout or paint at all. It is a fluid simulation living entirely on the GPU — five shader passes per frame, composited straight to screen.

Solver parameters

Press ` for all twelve

05 — Text

Shaping glyphs, then drawing them

Text is the most expensive thing most pages draw. Before a single pixel, the browser must shape it: map characters to glyphs, apply ligatures and kerning, and resolve the script's own rules. Only then does it rasterise, usually into a glyph atlas it can reuse.

Variable fonts change the arithmetic. Instead of shipping nine weight files the browser gets one outline plus axes, and interpolates. Fewer requests, less memory — but every distinct axis value is a new glyph to rasterise and cache.

Scroll and all four axes move together. Each card below locks the same letterform to one axis so you can isolate what it contributes.

Render

Scroll progress 0% — drives every axis

font-variation-settings: …

06 — The other thread

The thread that never waits for you

Everything so far shares one main thread, and a long task on it delays the next frame. Audio does not get that luxury. A dropped frame is a flicker; a dropped audio buffer is an audible click, so Web Audio runs on its own real-time thread with its own deadline.

Your rendering code cannot block it and it cannot block your rendering code. The analyser below is the handoff point — the audio thread fills a buffer, and the main thread reads whatever is current when it happens to draw.

Nothing is recorded, stored or sent. The stream stops the moment you switch it off, and the browser's own indicator is the proof.

Microphone off

Loudest — · peak — · 0 bins

07 — Measure

Now go and measure your own

None of this is worth memorising. What is worth building is the habit of looking — because the stage you assume is slow almost never is.

The counters below are live on this page, gathered with the same browser APIs you can use on yours.

This page, right now

0long tasks > 50 ms
—worst frame
—frames over budget
—JS heap

Sampled continuously since load · 0 frames seen

  1. Open DevTools → PerformanceRecord a few seconds of the interaction that feels wrong, not the page load.
  2. Read the flame chart top-downWide bars are the cost. Purple is layout and paint, yellow is your script.
  3. Look for the red cornersChrome marks long tasks and forced synchronous layout for you. Start there.
  4. Fix the widest bar onlyThen measure again. Optimising anything else is guessing.
// long tasks, in three lines, on any page
new PerformanceObserver(list => {
  for (const e of list.getEntries()) console.log(e.duration.toFixed(1) + 'ms', e.name);
}).observe({type: 'longtask', buffered: true});

Change the load, watch the cost

Every control here alters how much work this page does per frame. Turn the fluid off and the GPU stage empties; raise dust and the raster cost climbs. The counters above respond.

Accent

Behaviour

Atmosphere

Saved to this browser only

Colophon

One HTML file, no build step

This page argues that the browser is more capable than we usually let it be, so it would be poor form to ship it through four layers of tooling. View source and you can read the whole thing: shaders as template strings, the solver, the map geometry, the audio graph.

The only request beyond the fonts is the hand-tracking model, and that is fetched lazily — never unless you ask for it.

CompositeWebGL 2Ping-pong framebuffers at half resolution, HDR dye, ACES tone map
SimulationNavier–StokesSemi-Lagrangian advection, Jacobi pressure solve, vorticity confinement
PaintCanvas 2DParticle field and spectrum bars, redrawn per frame
TextFrauncesFour variable axes driven by scroll position
AudioWeb Audio2048-point FFT on its own real-time thread
InputMediaPipe21 hand landmarks from the webcam, loaded on request only
MeasurementPerformanceObserverLong tasks, frame times and heap, sampled live
Written by Shagun Mishra LinkedIn shagun-mishra
Shagun Mishra LinkedIn Bangalore, India