Wie viele Pixel hat eine Leinwand wirklich?
Seit Chrome 84 unterstützt ResizeObserver eine neue Feldmessung namens devicePixelContentBox
, mit der die Abmessungen des Elements in physischen Pixeln gemessen werden. So können pixelgenaue Grafiken gerendert werden, insbesondere auf Displays mit hoher Pixeldichte.
Hintergrund: CSS-Pixel, Canvas-Pixel und physische Pixel
Wir arbeiten zwar oft mit abstrakten Längeneinheiten wie em
, %
oder vh
, aber letztendlich geht es immer um Pixel. Wenn wir die Größe oder Position eines Elements in CSS angeben, konvertiert die Layout-Engine des Browsers diesen Wert schließlich in Pixel (px
). Das sind „CSS-Pixel“, die eine lange Geschichte haben und nur eine lose Beziehung zu den Pixeln auf Ihrem Bildschirm haben.
Lange Zeit war es relativ einfach, die Pixeldichte eines Bildschirms mit 96 DPI („dots per inch“, deutsch: Punkte pro Zoll) zu schätzen. Das bedeutet, dass ein beliebiger Monitor etwa 38 Pixel pro Zentimeter hat. Im Laufe der Zeit wurden Monitore größer und/oder kleiner oder hatten mehr Pixel auf derselben Fläche. Da viele Inhalte im Web ihre Abmessungen, einschließlich Schriftgrößen, in px
definieren, ist der Text auf diesen Bildschirmen mit hoher Pixeldichte („HiDPI“) oft schwer lesbar. Als Gegenmaßnahme blenden Browser die tatsächliche Pixeldichte des Monitors aus und geben stattdessen vor, dass der Nutzer ein Display mit 96 DPI hat. Die Einheit px
in CSS steht für die Größe eines Pixels auf diesem virtuellen 96-DPI-Display, daher der Name „CSS-Pixel“. Diese Einheit wird nur für Messungen und die Positionierung verwendet. Bevor das Rendering erfolgt, werden die Einheiten in physische Pixel umgerechnet.
Wie gelangen wir von dieser virtuellen Anzeige zum tatsächlichen Display des Nutzers? Geben Sie devicePixelRatio
ein. Dieser globale Wert gibt an, wie viele physische Pixel für ein einzelnes CSS-Pixel erforderlich sind. Wenn devicePixelRatio
(Geräte-Pixelverhältnis) 1
ist, arbeiten Sie auf einem Monitor mit etwa 96 DPI. Wenn Sie ein Retina-Display haben, ist Ihr dPR wahrscheinlich 2
. Auf Smartphones sind höhere (und ungewöhnlichere) dPR-Werte wie 2
, 3
oder sogar 2.65
nicht ungewöhnlich. Dieser Wert ist exakt, lässt aber keine Rückschlüsse auf den tatsächlichen DPI-Wert des Monitors zu. Ein dPR von 2
bedeutet, dass 1 CSS-Pixel genau 2 physischen Pixeln entspricht.
1
…Es hat eine Breite von 3.440 Pixeln und der Anzeigebereich ist 79 cm breit.
Das führt zu einer Auflösung von 110 DPI. Fast 96, aber nicht ganz.
Das ist auch der Grund, warum ein <div style="width: 1cm; height: 1cm">
auf den meisten Displays nicht genau 1 cm groß ist.
Schließlich kann sich auch die Zoomfunktion Ihres Browsers auf den dPR auswirken. Wenn Sie heranzoomen, erhöht der Browser den gemeldeten DPR-Wert, sodass alles größer gerendert wird. Wenn Sie devicePixelRatio
in der DevTools-Konsole prüfen, während Sie zoomen, werden Bruchwerte angezeigt.

devicePixelRatio
angezeigt.Fügen wir das Element <canvas>
hinzu. Mit den Attributen width
und height
können Sie angeben, wie viele Pixel das Canvas haben soll. <canvas width=40 height=30>
wäre also ein Canvas mit 40 × 30 Pixeln. Das bedeutet jedoch nicht, dass es mit 40 × 30 Pixeln angezeigt wird. Standardmäßig wird für die intrinsische Größe des Canvas das Attribut width
und height
verwendet. Sie können die Größe des Canvas aber auch mit allen bekannten CSS-Properties anpassen. Nach allem, was wir bisher gelernt haben, ist Ihnen vielleicht schon klar, dass dies nicht in jedem Szenario ideal ist. Ein Pixel auf dem Canvas kann mehrere physische Pixel oder nur einen Bruchteil eines physischen Pixels abdecken. Dies kann zu unschönen visuellen Artefakten führen.
Zusammenfassend lässt sich sagen, dass Canvas-Elemente eine bestimmte Größe haben, um den Bereich zu definieren, in dem Sie zeichnen können. Die Anzahl der Canvas-Pixel ist völlig unabhängig von der Anzeigegröße des Canvas, die in CSS-Pixeln angegeben wird. Die Anzahl der CSS-Pixel ist nicht mit der Anzahl der physischen Pixel identisch.
Pixelgenauigkeit
In einigen Fällen ist es wünschenswert, eine genaue Zuordnung von Canvas-Pixeln zu physischen Pixeln zu haben. Wenn diese Zuordnung erreicht wird, spricht man von „pixelgenau“. Die pixelgenaue Darstellung ist entscheidend für die lesbare Darstellung von Text, insbesondere bei Verwendung von Subpixel-Rendering oder bei der Darstellung von Grafiken mit eng ausgerichteten Linien mit abwechselnder Helligkeit.
Um im Web ein möglichst pixelgenaues Canvas zu erhalten, war dies mehr oder weniger der Standardansatz:
<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>
Der aufmerksame Leser fragt sich vielleicht, was passiert, wenn dPR kein ganzzahliger Wert ist. Das ist eine gute Frage und genau hier liegt der Knackpunkt des Problems. Wenn Sie die Position oder Größe eines Elements mit Prozentangaben, vh
oder anderen indirekten Werten angeben, kann es außerdem sein, dass sie in CSS-Pixelbruchteile aufgelöst werden. Ein Element mit margin-left: 33%
kann so aussehen:

getBoundingClientRect()
-Aufrufs.CSS-Pixel sind rein virtuell. Daher sind Bruchteile eines Pixels theoretisch in Ordnung. Aber wie ordnet der Browser sie physischen Pixeln zu? Da es keine physischen Pixel mit Bruchteilen gibt.
Pixel-Snapping
Der Teil des Einheitenkonvertierungsprozesses, der für die Ausrichtung von Elementen an physischen Pixeln zuständig ist, wird als „Pixel-Snapping“ bezeichnet. Dabei werden gebrochene Pixelwerte in ganzzahlige, physische Pixelwerte umgewandelt. Wie genau das geschieht, ist von Browser zu Browser unterschiedlich. Wenn wir ein Element mit einer Breite von 791.984px
auf einem Display mit einem Gerätepixelverhältnis von 1 haben, rendert ein Browser das Element möglicherweise mit 792px
physischen Pixeln, ein anderer Browser mit 791px
. Das ist nur ein Pixel, aber ein einzelnes Pixel kann sich negativ auf Renderings auswirken, die pixelgenau sein müssen. Das kann zu Unschärfe oder noch sichtbaren Artefakten wie dem Moiré-Effekt führen.

(Möglicherweise müssen Sie dieses Bild auf einem neuen Tab öffnen, um es ohne Skalierung zu sehen.)
devicePixelContentBox
devicePixelContentBox
gibt die Contentbox eines Elements in Gerätepixeln (d.h. physischen Pixeln) an. Sie ist Teil von ResizeObserver
. ResizeObserver wird seit Safari 13.1 in allen wichtigen Browsern unterstützt, die devicePixelContentBox
-Eigenschaft ist derzeit jedoch nur in Chrome 84 und höher verfügbar.
Wie in ResizeObserver
: it's like document.onresize
for elements erwähnt, wird die Callback-Funktion von ResizeObserver
vor dem Rendern und nach dem Layout aufgerufen. Das bedeutet, dass der Parameter entries
für den Callback die Größen aller beobachteten Elemente kurz vor dem Rendern enthält. Im Zusammenhang mit dem oben beschriebenen Problem mit dem Canvas können wir die Gelegenheit nutzen, die Anzahl der Pixel auf dem Canvas anzupassen, um eine genaue 1:1-Zuordnung zwischen Canvas-Pixeln und physischen Pixeln zu erreichen.
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']});
Mit dem Attribut box
im Optionenobjekt für observer.observe()
können Sie festlegen, welche Größen beobachtet werden sollen. Jede ResizeObserverEntry
liefert also immer borderBoxSize
, contentBoxSize
und devicePixelContentBoxSize
(sofern der Browser dies unterstützt). Der Callback wird jedoch nur aufgerufen, wenn sich einer der beobachteten Messwerte für das Feld ändert.
Mit dieser neuen Eigenschaft können wir sogar die Größe und Position unseres Canvas animieren (was effektiv Bruchpixelwerte garantiert), ohne dass Moiré-Effekte beim Rendern auftreten. Wenn Sie den Moiré-Effekt bei der Verwendung von getBoundingClientRect()
sehen möchten und erfahren möchten, wie Sie ihn mit der neuen Eigenschaft ResizeObserver
vermeiden können, sehen Sie sich die Demo in Chrome 84 oder höher an.
Funktionserkennung
Um zu prüfen, ob der Browser eines Nutzers devicePixelContentBox
unterstützt, können wir ein beliebiges Element beobachten und prüfen, ob die Eigenschaft im ResizeObserverEntry
vorhanden ist:
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
}
Fazit
Pixel sind ein überraschend komplexes Thema im Web. Bisher gab es keine Möglichkeit, die genaue Anzahl der physischen Pixel zu ermitteln, die ein Element auf dem Bildschirm des Nutzers einnimmt. Das neue Attribut devicePixelContentBox
für ein ResizeObserverEntry
liefert diese Information und ermöglicht pixelgenaue Renderings mit <canvas>
. devicePixelContentBox
wird in Chrome 84 und höher unterstützt.