¿Cuántos píxeles hay realmente en un lienzo?
Desde Chrome 84, ResizeObserver admite una nueva medición de caja llamada devicePixelContentBox
, que mide la dimensión del elemento en píxeles físicos. Esto permite renderizar gráficos con una precisión de píxeles, especialmente en el contexto de pantallas de alta densidad.
Antecedentes: Píxeles de CSS, píxeles de lienzo y píxeles físicos
Si bien a menudo trabajamos con unidades de longitud abstractas, como em
, %
o vh
, todo se reduce a píxeles. Cada vez que especificamos el tamaño o la posición de un elemento en CSS, el motor de diseño del navegador finalmente convertirá ese valor en píxeles (px
). Estos son los "píxeles de CSS", que tienen mucha historia y solo tienen una relación vaga con los píxeles que tienes en la pantalla.
Durante mucho tiempo, fue bastante razonable estimar la densidad de píxeles de la pantalla de cualquier persona con 96 DPI (puntos por pulgada), lo que significa que cualquier monitor determinado tendría aproximadamente 38 píxeles por cm. Con el tiempo, los monitores crecieron o se encogieron, o bien comenzaron a tener más píxeles en la misma superficie. Si a eso le sumamos el hecho de que gran parte del contenido en la Web define sus dimensiones, incluidos los tamaños de fuente, en px
, terminamos con texto ilegible en estas pantallas de alta densidad ("HiDPI"). Como medida de protección, los navegadores ocultan la densidad de píxeles real del monitor y, en cambio, simulan que el usuario tiene una pantalla de 96 DPI. La unidad px
en CSS representa el tamaño de un píxel en esta pantalla virtual de 96 DPI, de ahí el nombre "píxel de CSS". Esta unidad solo se usa para la medición y el posicionamiento. Antes de que se realice cualquier renderización real, se produce una conversión a píxeles físicos.
¿Cómo pasamos de esta pantalla virtual a la pantalla real del usuario? Ingresa devicePixelRatio
. Este valor global te indica cuántos píxeles físicos necesitas para formar un solo píxel de CSS. Si devicePixelRatio
(dPR) es 1
, estás trabajando en un monitor con aproximadamente 96 DPI. Si tienes una pantalla Retina, es probable que tu dPR sea 2
. En los teléfonos, no es raro encontrar valores de dPR más altos (y extraños) como 2
, 3
o incluso 2.65
. Es fundamental tener en cuenta que este valor es exacto, pero no te permite derivar el valor de DPI real del monitor. Un dPR de 2
significa que 1 píxel en CSS se asignará a exactamente 2 píxeles físicos.
1
…Tiene 3,440 píxeles de ancho y el área de visualización mide 79 cm de ancho.
Esto genera una resolución de 110 DPI. Cerca de 96, pero no exactamente.
Ese es también el motivo por el que un <div style="width: 1cm; height: 1cm">
no medirá exactamente 1 cm en la mayoría de las pantallas.
Por último, el dPR también puede verse afectado por la función de zoom de tu navegador. Si acercas la imagen, el navegador aumenta el dPR informado, lo que hace que todo se renderice más grande. Si verificas devicePixelRatio
en una consola de Herramientas para desarrolladores mientras haces zoom, puedes ver que aparecen valores fraccionarios.

devicePixelRatio
fraccionarios debido al zoom.Agreguemos el elemento <canvas>
. Puedes especificar cuántos píxeles quieres que tenga el lienzo con los atributos width
y height
. Por lo tanto, <canvas width=40 height=30>
sería un lienzo de 40 por 30 píxeles. Sin embargo, esto no significa que se mostrará con un tamaño de 40 x 30 píxeles. De forma predeterminada, el lienzo usará los atributos width
y height
para definir su tamaño intrínseco, pero puedes cambiar el tamaño del lienzo de forma arbitraria con todas las propiedades de CSS que conoces y te encantan. Con todo lo que aprendimos hasta ahora, es posible que se te ocurra que esto no será ideal en todas las situaciones. 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.
En resumen, los elementos Canvas tienen un tamaño determinado para definir el área en la que puedes dibujar. La cantidad de píxeles del lienzo es completamente independiente del tamaño de visualización del lienzo, que se especifica en píxeles de CSS. La cantidad de píxeles de CSS no es la misma que la cantidad de píxeles físicos.
Perfección de píxeles
En algunas situaciones, es conveniente tener una asignación exacta de los píxeles del lienzo a los píxeles físicos. Si se logra esta asignación, se denomina "perfecta en píxeles". La renderización perfecta a nivel de píxel es fundamental para que el texto se renderice de forma legible, en especial cuando se usa la renderización de subpíxeles o cuando se muestran gráficos con líneas muy alineadas de brillo alternado.
Para lograr algo lo más parecido posible a un lienzo perfecto en píxeles en la Web, este ha sido el enfoque más o menos habitual:
<style>
/* … styles that affect the canvas' size … */
</style>
<canvas id="myCanvas"></canvas>
<script>
const cvs = document.querySelector('#myCanvas');
// Get the canvas' size in CSS pixels
const rectangle = cvs.getBoundingClientRect();
// Convert it to real pixels. Ish.
cvs.width = rectangle.width * devicePixelRatio;
cvs.height = rectangle.height * devicePixelRatio;
// Start drawing…
</script>
Es posible que el lector perspicaz se pregunte qué sucede cuando el dPR no es un valor entero. Esa es una buena pregunta y, precisamente, el quid de todo este problema. Además, si especificas la posición o el tamaño de un elemento con porcentajes, vh
o cualquier otro valor indirecto, es posible que se resuelvan en valores de píxeles de CSS fraccionarios. Un elemento con margin-left: 33%
puede terminar con un rectángulo como el siguiente:

getBoundingClientRect()
.Los píxeles de CSS son puramente virtuales, por lo que tener fracciones de un píxel está bien en teoría, pero ¿cómo determina el navegador la asignación a los píxeles físicos? Esto se debe a que los píxeles físicos fraccionarios no existen.
Ajuste de píxeles
La parte del proceso de conversión de unidades que se encarga de alinear los elementos con los píxeles físicos se denomina "ajuste de píxeles", y hace lo que su nombre indica: ajusta los valores de píxeles fraccionarios a valores de píxeles físicos enteros. La forma exacta en que esto sucede varía de un navegador a otro. Si tenemos un elemento con un ancho de 791.984px
en una pantalla en la que el DPR es 1, un navegador podría renderizar el elemento en 792px
píxeles físicos, mientras que otro navegador podría renderizarlo en 791px
. Eso es solo un píxel de diferencia, pero un solo píxel puede ser perjudicial para las renderizaciones que deben ser perfectas en términos de píxeles. Esto puede provocar imágenes borrosas o artefactos aún más visibles, como el efecto muaré.

(Es posible que debas abrir esta imagen en una pestaña nueva para verla sin que se le aplique ningún ajuste de escala).
devicePixelContentBox
devicePixelContentBox
te proporciona la caja de contenido de un elemento en unidades de píxeles del dispositivo (es decir, píxeles físicos). Es parte de ResizeObserver
. Si bien ResizeObserver ahora es compatible con todos los navegadores principales desde Safari 13.1, la propiedad devicePixelContentBox
solo está disponible en Chrome 84 y versiones posteriores por el momento.
Como se mencionó en ResizeObserver
: Es como document.onresize
para elementos, se llamará a la función de devolución de llamada de un ResizeObserver
antes de la pintura y después del diseño. Esto significa que el parámetro entries
de la devolución de llamada contendrá los tamaños de todos los elementos observados justo antes de que se pinten. En el contexto del problema de lienzo que se describió anteriormente, podemos aprovechar esta oportunidad para ajustar la cantidad de píxeles en nuestro lienzo y asegurarnos de que terminemos con un mapeo exacto uno a uno entre los píxeles del lienzo y los píxeles físicos.
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;
/* … render to canvas … */
});
observer.observe(canvas, {box: ['device-pixel-content-box']});
La propiedad box
del objeto de opciones para observer.observe()
te permite definir qué tamaños deseas observar. Por lo tanto, si bien cada ResizeObserverEntry
siempre proporcionará borderBoxSize
, contentBoxSize
y devicePixelContentBoxSize
(siempre que el navegador lo admita), la devolución de llamada solo se invocará si cambia alguna de las métricas de la caja observadas.
Con esta nueva propiedad, incluso podemos animar el tamaño y la posición de nuestro lienzo (lo que garantiza de manera efectiva los valores de píxeles fraccionarios) y no ver ningún efecto muaré en la renderización. Si quieres ver el efecto muaré en el enfoque con getBoundingClientRect()
y cómo la nueva propiedad ResizeObserver
te permite evitarlo, consulta la demostración en Chrome 84 o versiones posteriores.
Detección de características
Para verificar si el navegador de un usuario admite devicePixelContentBox
, podemos observar cualquier elemento y verificar si la propiedad está presente en el 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())) {
// The browser does NOT support devicePixelContentBox
}
Conclusión
Los píxeles son un tema sorprendentemente complejo en la Web y, hasta ahora, no había forma de saber la cantidad exacta de píxeles físicos que ocupa un elemento en la pantalla del usuario. La nueva propiedad devicePixelContentBox
en un ResizeObserverEntry
te brinda esa información y te permite realizar renderizaciones perfectas con <canvas>
. devicePixelContentBox
es compatible con Chrome 84 y versiones posteriores.