Calibration with a physical ruler
The browser doesn't know the real size of the screen. Use a real ruler on the glass and match the virtual ruler to it. Once they line up, we know the DPI exactly.
No ruler? You can use a bank card, which has a fixed size of 85.60 × 53.98 mm (the ISO ID-1 standard). On the horizontal ruler, bring its long edge (85.60 mm) onto the orange line. On the vertical one, rest the card on the orange box until they match.
Horizontal DPI: —
Vertical DPI: —
Effective DPI
Horizontal DPI
Vertical DPI
CSS ppi (what the browser sees)
DPR
Pinch zoom (visualViewport)
Physical size
Diagonal
| pixel type | 1 pixel in | 1 mm in | ||
|---|---|---|---|---|
| inch | mm | pt | pixel | |
| css pixel | 1 / — | — | — | — |
| logical pixel | = css1 | — | — | — |
| device pixel | 1 / — | — | — | — |
| hardware pixel | = device2 | ? | ? | ? |
1 at 100% zoom · 2 when the system renders at the native resolution
The browser doesn't know the physical size of the screen. With the ruler you give it that information: when the virtual marks fall on the real ones, we know how many CSS pixels cover 1 real centimetre. That figure, call it p (px/cm), is the whole basis of the calculation.
Four 'pixels' that often get confused, from the most abstract to the most physical:
| Name | What it is | Fixed? | Do we have it? |
|---|---|---|---|
| css pixel | the browser's logical unit for layout | No, changes with zoom | ✓1 |
| logical pixel | OS logical pixel (point / DIP) | No, changes with scaling | ✓ |
| device pixel | the surface the browser renders to (css/logical × DPR) | No, changes with scaling + zoom | ✓2 |
| hardware pixel | the hardware dot, the screen's native resolution | Yes, baked into the hardware | ✗3 |
1 css pixel: you measure it here, with the ruler · 2 device pixel: computed, not the panel itself · 3 hardware pixel: no access, we don't measure it
The browser does not see the physical screen as it really is. Its physical size (how many inches) is entirely unknown to it, while it knows the resolution in CSS pixels (window.screen), affected by the operating system's scaling and by zoom, not the device pixels. The DPR takes us to the device pixels, but it blends the screen's density together with the zoom.
So because the physical size is missing, the DPI cannot be derived on its own: we measure it with the ruler. The result is the effective DPI, the density of the device pixels (how many per inch) on the glass (equal to the hardware pixels when the system renders at the native resolution, with no intermediate scaling), which often differs from what the browser «assumes» (the CSS default of 96 ppi*).
* CSS fixes 1 inch = 96 CSS pixels (so 1px = 1/96 inch, 1pt = 1.333px, 1cm ≈ 37.8px). So for CSS purposes the browser «assumes» every screen is 96 ppi, regardless of its real density. If you write width: 1in, you get 96 CSS pixels, not necessarily a real inch on the glass.
One inch = 2.54 cm, and each CSS pixel maps to DPR device pixels. So the device pixels per inch (the DPI) are:
With pinch-zoom (touch or trackpad) the visualViewport.scale factor is added too; page zoom (Ctrl +/−) is already inside the DPR:
We measure it separately for the horizontal and the vertical ruler and show the average:
In your measurement: p ≈ — px/cm · DPR — → DPI ≈ — ppi
Knowing how many CSS pixels = 1 inch (that is p × 2.54, the «CSS ppi»), the physical width comes from dividing the screen resolution (in CSS pixels) by it. Likewise the height:
And the diagonal from the Pythagorean theorem:
In your measurement: w ≈ —″ · h ≈ —″ · D ≈ —″
Note: the physical size and the diagonal hold at the current zoom. The browser's page zoom (Ctrl +/−) shifts them, because window.screen.width does not follow the calibration when the zoom changes (and the zoom level is not detectable from the page). For an accurate value, measure at 100% zoom — the DPI stays correct anyway.
On high-density (retina) screens a CSS pixel is not one device pixel; it is DPR device ones (e.g. 2×2 = 4). The ruler measures in CSS pixels, so to reach the device pixels we multiply by the DPR. Without it, the DPI would come out half (the «CSS ppi»).
On a computer, page zoom (Ctrl and + or −) changes the DPR: at 200% the DPR doubles. But the browser does not expose a separate «zoom level», partly for privacy reasons (to prevent fingerprinting), so we only see the combined DPR, which mixes the screen density with the zoom. This is exactly why the ruler calibration is needed: only that way can we separate the real DPI from the zoom.
On tablets and phones, pinch-zoom does not change the DPR (it stays fixed, at the device's native value), but we read it from visualViewport.scale. The same holds for a trackpad pinch on a computer. So whichever way you zoom, the calculation stays correct: we multiply the DPR by visualViewport.scale (where available) to find how many device pixels one CSS pixel finally maps to.
Right now on the page: DPR — · visualViewport.scale —