How many pixels are there For real on a canvas?
Since Chrome 84, ResizeObserver supports a new box measure called device-pixel-content-box
, which measures the dimension of the element in physical pixels. This enables pixel perfect graphics to be rendered, especially in the context of high-density displays.
Fondo: píxeles CSS, píxeles de lienzo y píxeles físicos
While we often work with abstract units of length like em
, %
or vh
, todo se reduce a píxeles. Siempre que especifiquemos el tamaño o la posición de un elemento en CSS, el motor de diseño del browser eventualmente convertirá ese valor en píxeles (px
). These are "CSS Pixels", which have a long history and only have a loose relationship to the pixels you have on your screen.
Durante mucho tiempo, fue bastante razonable estimar la densidad de píxeles de la pantalla de cualquier persona con 96 ppp («puntos por pulgada»), lo que significa que cualquier monitor tendría aproximadamente 38 píxeles por cm. Con el tiempo, los monitores crecieron y / o encogieron o empezaron a tener más píxeles en la misma superficie. Combine eso con el hecho de que una gran cantidad de contents on the Web define sus dimensiones, incluidos los tamaños de fuente, en px
, y terminamos con texto ilegible en estas pantallas de alta densidad («HiDPI»). Como contramedida, los browsers ocultan la densidad de píxeles real del monitor y, en su lugar, fingen que el Username tiene una pantalla de 96 DPI. los px
unit in CSS represents the size of a pixel in this virtual Pantalla de 96 DPI, de ahí el nombre «CSS Pixel». Esta unidad solo se utiliza para medición y positioning. Antes de que ocurra cualquier renderizado real, ocurre una conversion a píxeles físicos.
How do we go from this virtual screen to the user's real screen? Get in devicePixelRatio
. This global value tells you how many physical pixels you need to form a single CSS pixel. Yes devicePixelRatio
(dPR) is 1
, está trabajando en un monitor con aproximadamente 96 ppp. Si tiene una pantalla de retina, probablemente su dPR be 2
. On phones, it is not uncommon to find higher (and weirder) dPR values like 2
, 3
or even 2.65
. It is essential to take into account that this value is exactly, but does not allow you to bypass the monitor real DPI value. A dPR of 2
means 1 CSS pixel will be mapped to exactly 2 physical pixels.
My monitor has a dPR of 1
according to Chrome ...
1
according to Chrome ... It is 3440 pixels wide and the display area is 79 cm wide. That leads to a resolution of 110 DPI. Close to 96, but not quite. That is also the reason why <div style="width: 1cm; height: 1cm">
It will not be exactly 1cm in size on most screens.
Finally, dPR can also be affected by the zoom function of your browser. If you zoom in, the browser increases the reported dPR, making everything look bigger. If you see devicePixelRatio
In a DevTools Console while zooming, you can see fractional values appear.

devicePixelRatio
due to zoom.Let's add the element to the mix. You can specify how many pixels you want the canvas to be using the
width
and height
attributes. Then it would be a 40 by 30 pixel canvas. However, this does not mean that it will be unfolded at 40 by 30 pixels. By default, the canvas will use the
width
and height
attribute para definir su tamaño intrínseco, pero puede cambiar el tamaño del lienzo arbitrariamente utilizando todas las propiedades CSS que conoce y ama. Con todo lo que hemos aprendido hasta ahora, se le puede ocurrir que esto no será ideal en todos los escenarios. Un píxel en el lienzo puede terminar cubriendo varios píxeles físicos o solo una fracción de un píxel físico. Esto puede generar artefactos visuales desagradables.
To summarize: Elements on the canvas are sized to define the area in which you can draw. The number of pixels on the canvas is completely independent of the display size of the canvas, specified in CSS pixels. The number of CSS pixels is not the same as the number of physical pixels.
Pixel perfection
In some scenarios, it is desirable to have an exact mapping of the canvas pixels to the physical pixels. If this mapping is achieved, it is called "pixel perfect." Pixel-perfect rendering is crucial for legible rendering of text, especially when using sub-pixel representation or when displaying graphics with very aligned lines of alternating brightness.
To achieve something as close to a pixel perfect canvas as possible on the web, this has pretty much been the approach to take:
<style>
</style>
<canvas go="myCanvas"></canvas>
<script>
const cvs = document.querySelector('#myCanvas');
const rectangle = cvs.getBoundingClientRect();
cvs.width = rectangle.width * devicePixelRatio;
cvs.height = rectangle.height * devicePixelRatio;
</script>
The astute reader might wonder what happens when dPR is not an integer value. That's a good question and exactly where the crux of this whole problem lies. Also, if you specify the position or size of an item using percentages, vh
or other indirect values, they may be resolved to fractional CSS pixel values. An element with margin-left: 33%
you can end up with a rectangle like this:

getBoundingClientRect ()
call.CSS pixels are purely virtual, so having fractions of a pixel is fine in theory, but how does the browser calculate the allocation to physical pixels? Because fractional physical pixels are not a thing.
Pixel Adjustment
The part of the unit conversion process that takes care of aligning items to physical pixels is called “pixel snapping,” and it does what it says on the tin: it snaps fractional pixel values to whole physical pixel values. How exactly this happens is different from browser to browser. If we have an element with a width of 791.984px
on a screen where dPR is 1, a browser can render the element in 792px
physical pixels, while another browser may render it in 791px
. That's just one pixel less, but a single pixel can be detrimental to renderings that need to be pixel perfect. This can cause blurriness or even more visible artifacts such as Moiré effect.

(You may have to open this image in a new tab to view it without scaling it.)
devicePixelContentBox
devicePixelContentBox
gives you the content box of an item in units of device pixels (that is, physical pixels). It is part of ResizeObserver
. While ResizeObserver now supports all major browsers since Safari 13.1, the devicePixelContentBox
The property is only on Chrome 84+ for now.
As mentioned in ResizeObserver
: it is like document.onresize
for elements, the callback function of a ResizeObserver
it will be called before painting and after design. That means the entries
The callback parameter will contain the sizes of all the observed elements just before they are painted. In the context of our canvas problem described above, we can take this opportunity to adjust the number of pixels on our canvas, ensuring that we end up with an exact one-to-one mapping between the canvas pixels and the physical pixels.
const observer = new ResizeObserver((entries) => {
const entry = entries.find((entry) => entry.target === canvas);
canvas.width = entry.devicePixelContentBoxSize[0].inlineSize;
canvas.height = entry.devicePixelContentBoxSize[0].blockSize;
});
observer.observe(canvas, {box: ['device-pixel-content-box']});
the box
property on the options object for observe.observe ()
lets you define what sizes you want watch. So while each ResizeObserverEntry
will always provide borderBoxSize
, contentBoxSize
and devicePixelContentBoxSize
(as long as the browser supports it), the callback will only be invoked if any of the observed cash metrics change.
All cash metrics are matrices to allow ResizeObserver
to handle fragmentation in the future. At the time of writing this article, the array always has a length of 1.
With this new property, we can even animate the size and position of our canvas (effectively guaranteeing fractional pixel values) and not see any Moiré effects in the rendering. If you want to see the Moiré effect in focus using getBoundingClientRect ()
and like the new ResizeObserver
property allows you to avoid it, take a look at the manifestation on Chrome 84 or later.
Feature detection
To check if a user's browser is compatible with devicePixelContentBox
, we can observe any element and check if the property is present in the ResizeObserverEntry
:
function hasDevicePixelContentBox() {
return new Promise((resolve) => {
const ro = new ResizeObserver((entries) => {
resolve(entries.every((entry) => 'devicePixelContentBoxSize' in entry));
ro.disconnect();
});
ro.observe(document.body, {box: ['device-pixel-content-box']});
}).catch(() => false);
}if (!(await hasDevicePixelContentBox())) {
}
conclusion
Pixels are a surprisingly complex subject on the web and until now there was no way to know the exact number of physical pixels an item occupies on the user's screen. The new devicePixelContentBox
property in a ResizeObserverEntry
gives you that information and allows you to render pixel perfect renderings with .
devicePixelContentBox
it is compatible with Chrome 84+.