Ile pikseli naprawdę znajduje się na obszarze roboczym?
Od Chrome 84 interfejs ResizeObserver obsługuje nowy pomiar pola o nazwie devicePixelContentBox
, który mierzy wymiary elementu w fizycznych pikselach. Umożliwia to renderowanie grafiki z dokładnością do piksela, zwłaszcza w przypadku ekranów o wysokiej gęstości pikseli.
Tło: piksele CSS, piksele obszaru rysowania i piksele fizyczne
Chociaż często pracujemy z abstrakcyjnymi jednostkami długości, takimi jak em
, %
czy vh
, wszystko sprowadza się do pikseli. Gdy w CSS określamy rozmiar lub położenie elementu, silnik układu przeglądarki ostatecznie przekształca tę wartość na piksele (px
). Są to „piksele CSS”, które mają długą historię i tylko luźny związek z pikselami na ekranie.
Przez długi czas można było dość dokładnie oszacować gęstość pikseli na ekranie dowolnego urządzenia na 96 DPI (punktów na cal), co oznaczało, że każdy monitor miał około 38 pikseli na cm. Z czasem monitory rosły lub malały albo zaczęły mieć więcej pikseli na tej samej powierzchni. W połączeniu z faktem, że wiele treści w internecie definiuje swoje wymiary, w tym rozmiary czcionek, w px
, otrzymujemy nieczytelny tekst na ekranach o wysokiej gęstości pikseli (HiDPI). W odpowiedzi na to przeglądarki ukrywają rzeczywistą gęstość pikseli monitora i zamiast tego udają, że użytkownik ma wyświetlacz o rozdzielczości 96 DPI. Jednostka px
w CSS reprezentuje rozmiar 1 piksela na tym wirtualnym wyświetlaczu o rozdzielczości 96 DPI, stąd nazwa „piksel CSS”. To urządzenie służy tylko do pomiarów i określania pozycji. Zanim nastąpi faktyczne renderowanie, następuje konwersja na piksele fizyczne.
Jak przejść od tego wirtualnego wyświetlacza do rzeczywistego wyświetlacza użytkownika? Wpisz devicePixelRatio
. Ta wartość globalna określa, ile fizycznych pikseli jest potrzebnych do utworzenia jednego piksela CSS. Jeśli devicePixelRatio
(dPR) ma wartość 1
, pracujesz na monitorze o rozdzielczości około 96 DPI. Jeśli masz ekran Retina, wartość dPR wynosi prawdopodobnie 2
. Na telefonach często spotyka się wyższe (i dziwniejsze) wartości dPR, np. 2
, 3
lub nawet 2.65
. Pamiętaj, że ta wartość jest dokładna, ale nie pozwala na określenie rzeczywistej wartości DPI monitora. Wartość dPR równa 2
oznacza, że 1 piksel CSS odpowiada dokładnie 2 pikselom fizycznym.
1
według Chrome…Ma 3440 pikseli szerokości, a obszar wyświetlania ma 79 cm szerokości.
Daje to rozdzielczość 110 DPI. Blisko 96, ale nie do końca.
Dlatego też <div style="width: 1cm; height: 1cm">
nie będzie miał na większości wyświetlaczy dokładnie 1 cm.
Na wartość dPR może też wpływać funkcja powiększania w przeglądarce. Jeśli powiększysz widok, przeglądarka zwiększy zgłoszony współczynnik dPR, co spowoduje powiększenie wszystkich elementów. Jeśli podczas powiększania zaznaczysz devicePixelRatio
w konsoli Narzędzi deweloperskich, zobaczysz wartości ułamkowe.

devicePixelRatio
z powodu powiększenia.Dodajmy do tego element <canvas>
. Za pomocą atrybutów width
i height
możesz określić liczbę pikseli, które ma mieć obszar roboczy. W tym przypadku <canvas width=40 height=30>
to obszar o wymiarach 40 x 30 pikseli. Nie oznacza to jednak, że będzie wyświetlana w rozmiarze 40 × 30 pikseli. Domyślnie element canvas używa atrybutów width
i height
do określania swojego rozmiaru wewnętrznego, ale możesz dowolnie zmieniać jego rozmiar za pomocą wszystkich znanych Ci właściwości CSS. Z tego, czego się dotychczas dowiedzieliśmy, może wynikać, że nie w każdej sytuacji będzie to idealne rozwiązanie. Jeden piksel na płótnie może pokrywać wiele pikseli fizycznych lub tylko ich część. Może to prowadzić do nieestetycznych artefaktów wizualnych.
Podsumowując: elementy Canvas mają określony rozmiar, który definiuje obszar, na którym można rysować. Liczba pikseli obszaru roboczego jest całkowicie niezależna od rozmiaru wyświetlania obszaru roboczego podanego w pikselach CSS. Liczba pikseli CSS nie jest taka sama jak liczba pikseli fizycznych.
Perfekcja Pixela
W niektórych przypadkach pożądane jest dokładne mapowanie pikseli obszaru rysowania na piksele fizyczne. Jeśli takie mapowanie zostanie osiągnięte, nazywa się je „idealnym”. Precyzyjne renderowanie jest kluczowe dla czytelnego renderowania tekstu, zwłaszcza w przypadku korzystania z renderowania subpikselowego lub wyświetlania grafiki z ściśle dopasowanymi liniami o naprzemiennej jasności.
Aby uzyskać w internecie efekt jak najbardziej zbliżony do idealnego płótna, stosuje się mniej więcej takie podejście:
<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>
Uważny czytelnik może się zastanawiać, co się stanie, gdy dPR nie będzie liczbą całkowitą. To dobre pytanie, które dotyka sedna całego problemu. Jeśli określisz pozycję lub rozmiar elementu za pomocą wartości procentowych, vh
lub innych wartości pośrednich, mogą one zostać przekształcone na ułamkowe wartości pikseli CSS. Element z margin-left: 33%
może mieć postać prostokąta, takiego jak ten:

getBoundingClientRect()
.Piksele CSS są czysto wirtualne, więc ułamki piksela są teoretycznie dopuszczalne, ale jak przeglądarka określa mapowanie na piksele fizyczne? Ponieważ nie ma ułamkowych pikseli fizycznych.
Przyciąganie do pikseli
Część procesu konwersji jednostek, która odpowiada za wyrównywanie elementów do pikseli fizycznych, nazywa się „przyciąganiem pikseli”. Jej działanie jest zgodne z nazwą: przyciąga ułamkowe wartości pikseli do całkowitych wartości pikseli fizycznych. Sposób działania tej funkcji różni się w zależności od przeglądarki. Jeśli mamy element o szerokości 791.984px
na wyświetlaczu, na którym dPR wynosi 1, jedna przeglądarka może renderować element w 792px
pikselach fizycznych, a inna w 791px
. To tylko 1 piksel różnicy, ale może to mieć negatywny wpływ na renderowanie, które musi być idealne. Może to prowadzić do rozmycia lub jeszcze bardziej widocznych artefaktów, takich jak efekt Moiré.

(Aby zobaczyć ten obraz bez skalowania, otwórz go w nowej karcie).
devicePixelContentBox
devicePixelContentBox
podaje pole zawartości elementu w jednostkach pikseli urządzenia (czyli pikseli fizycznych). Jest to część ResizeObserver
. Interfejs ResizeObserver jest obecnie obsługiwany we wszystkich głównych przeglądarkach od wersji Safari 13.1, ale właściwość devicePixelContentBox
jest na razie dostępna tylko w Chrome w wersji 84 lub nowszej.
Jak wspomnieliśmy w ResizeObserver
: to jak document.onresize
w przypadku elementów, funkcja wywołania zwrotnego ResizeObserver
zostanie wywołana przed rysowaniem i po układzie. Oznacza to, że parametr entries
przekazywany do funkcji zwrotnej będzie zawierać rozmiary wszystkich obserwowanych elementów tuż przed ich wyrenderowaniem. W kontekście opisanego powyżej problemu z płótnem możemy wykorzystać tę okazję, aby dostosować liczbę pikseli na płótnie i zapewnić dokładne odwzorowanie pikseli płótna na piksele fizyczne.
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']});
Właściwość box
w obiekcie opcji dla observer.observe()
umożliwia określenie rozmiarów, które chcesz obserwować. Każda wartość ResizeObserverEntry
zawsze będzie zawierać borderBoxSize
, contentBoxSize
i devicePixelContentBoxSize
(o ile przeglądarka to obsługuje), ale wywołanie zwrotne nastąpi tylko wtedy, gdy zmieni się którykolwiek z obserwowanych danych pola.
Dzięki tej nowej właściwości możemy nawet animować rozmiar i pozycję obszaru roboczego (co skutecznie gwarantuje wartości ułamkowe pikseli) i nie obserwować żadnych efektów Moiré podczas renderowania. Jeśli chcesz zobaczyć efekt Moiré w przypadku podejścia z użyciem getBoundingClientRect()
i dowiedzieć się, jak nowa właściwość ResizeObserver
pozwala go uniknąć, zapoznaj się z demonstracją w Chrome 84 lub nowszej wersji.
Wykrywanie cech
Aby sprawdzić, czy przeglądarka użytkownika obsługuje devicePixelContentBox
, możemy obserwować dowolny element i sprawdzić, czy właściwość jest obecna w 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
}
Podsumowanie
Piksele to zaskakująco złożony temat w internecie. Do tej pory nie było możliwości poznania dokładnej liczby pikseli fizycznych, które element zajmuje na ekranie użytkownika. Nowa właściwość devicePixelContentBox
w przypadku elementu ResizeObserverEntry
dostarcza tych informacji i umożliwia renderowanie z dokładnością do piksela za pomocą elementu <canvas>
. devicePixelContentBox
jest obsługiwana w Chrome 84 i nowszych wersjach.