Open emilio opened 4 years ago
This is also related to https://github.com/w3c/csswg-drafts/issues/4713, where we concluded the same thing: either make the existing API return doubles, or look into making new ones that do.
Another workaround seems to be this:
const dpr = window.devicePixelRatio;
const width = Math.floor( window.innerWidth * dpr ) / dpr;
const height = Math.floor( window.innerHeight * dpr ) / dpr;
But I vote for turning these into doubles too.
(And 4e888a204c)
@bokand
The CSS Working Group just discussed [cssom-view] No way to access the viewport size without losing precision.
, and agreed to the following:
RESOLVED: No change
FWIW, the issue that led to #4713 (https://crbug.com/1043456) was caused by Blink trying to make MQ truncate viewport dimensions like innerWidth is, since the latter is (often?) used in media queries. The change to make the MQ compare against a subpixel width made it to stable and the above bug was the only one I saw but we did revert it in the next milestone. Looking back at it, it's not even clear to me the behavior is unexpected or broke any production sites.
I'm curious to know if we could make viewport dimensions subpixel. I think it would simplify cases like the above.
Yes, the resolution above is "No change, pending Emilio trying to do it in Gecko (on Nightly / Beta) and reporting back if there's breakage or not".
I thought I would give more background to this issue...
Historically, many canvas/webgl projects configured the canvas
element size like this:
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
When window.devicePixelRatio
was introduced, this pattern was used to render at the physical resolution:
canvas.width = window.innerWidth * window.devicePixelRatio;
canvas.height = window.innerHeight * window.devicePixelRatio;
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
This worked fine until devices started to use non-integer DPRs. In devices with DPRs such as 1.25, 2.75, ... we can end up with canvas elements that are bigger than the viewport (forcing the scrollbar to show up if the developer didn't set overflow: hidden
).
I've made a demo to illustrate this: https://devicepixelratio.glitch.me/
The demo shows the (virtual) resolution provided by innerWidth
and innerHeight
and the computed physical resolution by taking dpr into account. I've also added an edge to edge canvas using that information and it draws a 1 pixel blue border inside:
Things break when DPRs aren't integers.
For example, Google's PixelBook Go has a DPR of 1.25 and depending on the window size the canvas element is bigger than the viewport so the outline gets clipped:
Google's Pixel 4a has a DPR of 2.75 and we see the same issue. In this case we know that the device's physical resolution is 1080px width. If we divide 1080 by 2.75 we end up with 392.72px which would be the value of window.innerWidth
if it was not an integer.
It's worth noting that this doesn't seem to be an issue on Mac/iOS devices because, as far as I can see, all the devices have integer DPRs (1.0, 2.0, 3.0).
https://bugzilla.mozilla.org/show_bug.cgi?id=1676843 has a patch to give this a shot.
Another interesting thing I just found out is that MacOS doesn't seem to support resizing windows to subpixel sizes. Does anyone know if this is the case too on Windows and/or Linux?
I'm moderately sure that all windowing systems work on device pixels, so you shouldn't be able to resize under 1 device pixel if that's what you mean.
Using the terms we're using here, MacOS seems to be using "css pixels" when resizing windows. If I resize a window, I can see the cursor moving, but the window only resizes when I reach the next "css pixel".
I think the use case @mrdoob explained above (sizing a canvas to be fullscreen) can also be solved via the existing ResizeObserver feature to observe pixel-snapped rects.
Ok, the patch above landed and next day I got two regressions in major web properties (WhatsApp and YouTube), so I don't think this is going to fly compat-wise unfortunately.
Unless there's a strong objection against it I think a new API is the way to go if we want to expose this. I'd go for innerWidthDouble
and innerHeightDouble
, wdyt?
I'd go for
innerWidthDouble
andinnerHeightDouble
, wdyt?
I don't have anything better to suggest right now, but one concern with these names is that "double" isn't a JS concept so it might feel strange to be referring to some value as a double in script.
What about properties that gives the physical pixels instead?
const cssWidth = window.innerPhysicalWidth / window.devicePixelRatio;
We generally don't expose actual device pixels in APIs, I think (but you could compute the device pixel width by multiplying by devicePixelRatio
...)
https://github.com/w3c/csswg-drafts/issues/9237 just resolved to create a new object for layout related bits. It seems that's a good opportunity to make something like window.layoutViewport.{width,height}
doubles, thus maybe fixing this? cc @bramus
It seems that's a good opportunity to make something like
window.layoutViewport.{width,height}
doubles, thus maybe fixing this?
YES!
The CSS Working Group just discussed [cssom-view] No way to access the viewport size without losing precision.
, and agreed to the following:
RESOLVED: add .width and .height as doubles to the layout viewport interface
(Apologies for the short drive-by comment. I am currently OOO.)
Another thing authors also want to know beforehand are the width/height of the small and large viewports.
I think we have a separate issue for that but if not, then maybe we could add large and small sub-objects that also have width and height properties?
@emilio @tabatkins
@bramus yeah that was mentioned on the minutes, that's https://github.com/w3c/csswg-drafts/issues/8709
Related to https://github.com/w3c/csswg-drafts/issues/5259.
I recently caused a compat issue in Firefox for making the layout viewport size more often a subpixel size. Here are the gory details, which amount basically to people making layout decisions based on
window.innerWidth
,window.innerHeight
and so on. That seems a totally reasonable thing to do.But turns out you can't do it in a sound way (the original page has issues in all browsers, just a different zoom levels / resolutions / etc). Well, I guess technically you can call
getBoundingClientRect()
on aposition: fixed; top: 0; left: 0; right: 0; bottom: 0
element, but that seems rather hackish.So, I think we should either:
double
s.Is there any context I'm missing? If not and people agree that this is better than the status quo, I'd be happy to experiment with this in Firefox and see what the compat impact might be.