deviceNormalPixelRatio: proposal for zoom-independent devicePixelRatio for HD Retina games
Posted on:This is a proposal for addition of a zoom-independent version of window.devicePixelRatio
to HTML5.
The issue
Before Firefox 18 and Chrome 25, to make a 3D (WebGL) game drawn in HD, we could set the scale in <meta name="viewport">
to 1 and multiply the size of 2D things (HUD elements, menus) by window.devicePixelRatio
.
However, since these versions of the browsers, devicePixelRatio
started to take browser zoom level into account.
While the change works perfectly for loading of high-resolution versions of <img>
images and background-image
s, it has been impossible to draw HUD elements in HD 3D games with correct sizes since that.
Glossary of this proposal
DPR – zoom-dependent window.devicePixelRatio
.
Old DPR – devicePixelRatio
behavior before Firefox 18 and Chrome 25.
New DPR – devicePixelRatio
behavior since Firefox 18 and Chrome 25.
Issue details
With the old DPR, when <meta name="viewport">
scale is 1, we could simply multiply the size of HUD elements by DPR to get resolution-independent size of the element (so that it doesn’t look too small too dense displays):
//drawing a 48dip cross at (16dip;16dip)
ctx.fillRect(32 * devicePixelRatio, 16 * devicePixelRatio, 16 * devicePixelRatio, 48 * devicePixelRatio);
ctx.fillRect(16 * devicePixelRatio, 32 * devicePixelRatio, 48 * devicePixelRatio, 16 * devicePixelRatio);
On screen, the cross would have the following size with the old behavior:
Display DPI | Zoom level | devicePixelRatio |
Output size on screen |
---|---|---|---|
96 (1x) | 100% | 1 | 48×48 |
96 (1x) | 200% | 1 | 96×96 |
96 (1x) | 400% | 1 | 192×192 |
192 (2x) | 100% | 2 | 96×96 |
192 (2x) | 200% | 2 | 192×192 |
192 (2x) | 400% | 2 | 384×384 |
This is fine, on high-DPI displays 384×384 looks like 192×192 on low-DPI displays, so it’s correctly zoomed by 400% (48 * 4 = 192).
However, with the new DPI, DPR is multiplied by the zoom level too, so if we multiply the size by DPR, we get the element scaled twice (in the script by DPR and in the browser by zoom):
Display DPI | Zoom level | devicePixelRatio |
Output size on screen |
---|---|---|---|
96 (1x) | 100% | 1 | 48×48 |
96 (1x) | 200% | 2 | 192×192 |
96 (1x) | 400% | 4 | 768×768 |
192 (2x) | 100% | 2 | 96×96 |
192 (2x) | 200% | 4 | 384×384 |
192 (2x) | 400% | 8 | 1536×1536 |
On high-DPI displays, 1536×1536 looks like 768×768 on low-DPI displays. 768×768 is 1600% larger than 48×48, while we only wanted to zoom by 400%.
So, as I said before, we get the element zoomed by zoomLevel^2 instead of zoomLevel.
Use case example
If you open WebQuake on a high-DPI display, you can see that the game is rendered in HD, but the HUD and the menus appear too small.
This is because I set <meta viewport> scale to 1. If I didn’t do this, the HUD size would be correct, but the game would be blurry.
What exactly I want is to render the game in HD while having the HUD drawn at correct size.
The solution
The solution is very simple.
A constant value which behaves the same way as the old DPR – doesn’t take zoom level into account.
Let it be named window.deviceNormalPixelRatio
or navigator.deviceNormalPixelRatio
.
Another important thing is to make it viewport scale-independent. It would have no point then, since it is to be used when viewport scale is forced to 1 (for manual scaling of selected elements).
It must be accessible from CSS queries too so we won’t have to use JavaScript to resize every element.
For HD image loading, the old devicePixelRatio
would be used, and for manual <meta name="viewport">
correction, deviceNormalPixelRatio
would be.
Another possible use
Along with DPI scaling, deviceNormalPixelRatio
can be used to retrieve zoom level in a cross-browser way (there’s no way to retrieve zoom level yet!):
var zoom = devicePixelRatio / deviceNormalPixelRatio;
Or, maybe the browser should just provide the zoom level as a window property, and you could use that in your calculation.
Exactly. It’s weird that such thing doesn’t exist yet.
A zoom level property would be most welcome! Detecting zoom level is currently impossible. Here is few links to illustrate our current miserable condition:
https://tombigel.github.io/detect-zoom/
http://stackoverflow.com/questions/1713771/how-to-detect-page-zoom-level-in-all-modern-browsers
What makes matter worse is that this is a, ehm, global web regression! In fact we used to be able to detect zoom level: http://stackoverflow.com/questions/6163174/detect-page-zoom-change-with-jquery-in-safari