캔버스에는 실제로 몇 개의 픽셀이 있나요?
Chrome 84부터 ResizeObserver는 실제 픽셀로 요소의 크기를 측정하는 devicePixelContentBox
라는 새로운 상자 측정을 지원합니다. 이를 통해, 특히 고밀도 화면의 컨텍스트에서 픽셀 단위의 그래픽을 완벽히 렌더링할 수 있습니다.
브라우저 지원
- 84
- 84
- 93
- x
배경: CSS 픽셀, 캔버스 픽셀, 실제 픽셀
em
, %
, vh
와 같은 추상적인 길이 단위를 사용하는 경우가 많지만 모두 픽셀로 축소됩니다. CSS에서 요소의 크기나 위치를 지정할 때마다 브라우저의 레이아웃 엔진은 결국 해당 값을 픽셀 (px
)로 변환합니다. 이들은 'CSS 픽셀'이며 많은 기록이 있으며 화면에 있는 픽셀과 느슨한 관계를 갖습니다.
오랫동안 모든 사람의 화면 픽셀 밀도는 96DPI ('인치당 도트 수')를 추정하는 것이 합리적이었습니다. 즉, 어떤 모니터가 cm당 약 38 픽셀이 될 것입니다. 시간이 지남에 따라 모니터가 증가 또는 축소되거나 동일한 표면 영역에 더 많은 픽셀이 있기 시작했습니다. 여기에 글꼴 크기 등 글꼴 크기를 비롯한 웹 콘텐츠의 크기가 px
로 정의되는 경우가 많다는 점과 고밀도 ('HiDPI') 화면에서 읽을 수 없는 텍스트가 생성됩니다. 이에 대한 조치로 브라우저는 모니터의 실제 픽셀 밀도를 숨기고 대신 사용자의 디스플레이가 96DPI인 것으로 가장합니다. CSS의 px
단위는 이 가상 96 DPI 디스플레이의 1픽셀 크기를 나타내므로 'CSS Pixel'이라고 합니다. 이 단위는 측정 및 위치 지정에만 사용됩니다. 실제 렌더링이 발생하기 전에 실제 픽셀로 변환됩니다.
이 가상 디스플레이에서 사용자의 실제 디스플레이로 어떻게 전환할 수 있을까요? devicePixelRatio
를 입력합니다. 이 전역 값은 단일 CSS 픽셀을 구성하는 데 필요한 실제 픽셀 수를 알려줍니다. devicePixelRatio
(dPR)이 1
이면 약 96DPI의 모니터에서 작업 중인 것입니다. 레티나 화면의 경우 dPR이 2
일 수 있습니다. 휴대전화에서는 2
, 3
또는 2.65
와 같이 더 높고 더 이상한 dPR 값이 표시되는 경우가 많습니다. 이 값은 정확하지만 모니터의 실제 DPI 값을 가져올 수는 없습니다. dPR이 2
이면 1개의 CSS 픽셀이 정확히 2개의 실제 픽셀에 매핑된다는 의미입니다.
1
입니다.너비가 3440픽셀이고 디스플레이 영역의 너비는 79cm입니다.
그러면 해상도가 110DPI가 됩니다. 96에 가깝지만 정답이 아닙니다.
이 때문에 <div style="width: 1cm; height: 1cm">
는 대부분의 디스플레이에서 정확히 1cm 크기를 측정하지 않습니다.
마지막으로 dPR은 브라우저의 확대/축소 기능도 영향을 받을 수 있습니다. 확대하면 브라우저는 보고된 dPR을 높여 모든 항목이 더 크게 렌더링됩니다. 확대/축소하는 동안 DevTools 콘솔에서 devicePixelRatio
를 선택하면 소수 값이 표시되는 것을 볼 수 있습니다.
<canvas>
요소를 믹스에 추가해 보겠습니다. width
및 height
속성을 사용하여 캔버스에 포함할 픽셀 수를 지정할 수 있습니다. 따라서 <canvas width=40 height=30>
는 40x30픽셀 크기의 캔버스가 됩니다. 하지만 40x30픽셀로 표시되는 것은 아닙니다. 기본적으로 캔버스는 width
및 height
속성을 사용하여 고유한 크기를 정의하지만, 알고 있고 좋아하는 모든 CSS 속성을 사용하여 캔버스의 크기를 임의로 조절할 수 있습니다. 지금까지 배운 내용을 살펴봤을 때, 이러한 접근 방식이 모든 시나리오에 적합하지는 않을 수도 있습니다. 캔버스의 한 픽셀이 여러 실제 픽셀을 덮거나 실제 픽셀의 일부만 덮을 수도 있습니다. 이로 인해 시각적 아티팩트가 마음에 들지 않을 수 있습니다.
요약: 캔버스 요소에는 그릴 수 있는 영역을 정의하는 지정된 크기가 있습니다. 캔버스 픽셀의 수는 CSS 픽셀로 지정된 캔버스의 표시 크기와 완전히 별개입니다. CSS 픽셀 수는 실제 픽셀 수와 다릅니다.
픽셀의 완성도
일부 시나리오에서는 캔버스 픽셀에서 실제 픽셀로 정확하게 매핑하는 것이 좋습니다. 이렇게 매핑하면 이를 '완벽한 픽셀'이라고 합니다. 완벽한 픽셀의 렌더링은 텍스트를 읽기 쉽게 렌더링하는 데 매우 중요합니다. 특히 하위 픽셀 렌더링을 사용하거나 밝기의 교차 선으로 그래픽을 표시하는 경우 더욱 그렇습니다.
웹에서 가능한 한 픽셀의 완벽한 캔버스에 근접하는 목표를 달성하기 위해 다음과 같은 접근 방식을 취했습니다.
<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>
예리한 독자는 dPR이 정수 값이 아니면 어떻게 되는지 궁금해할 수 있습니다. 이것은 좋은 질문이며 이 전체 문제의 핵심이 정확히 어디에 있는가입니다. 또한 비율, vh
또는 기타 간접 값을 사용하여 요소의 위치나 크기를 지정하면 이러한 값이 소수 CSS 픽셀 값으로 확인될 수 있습니다. margin-left: 33%
가 있는 요소는 다음과 같은 직사각형이 될 수 있습니다.
CSS 픽셀은 순전히 가상이므로 이론적으로는 픽셀의 일부가 있어도 괜찮지만, 브라우저는 실제 픽셀에 대한 매핑을 어떻게 알아낼까요? 분수의 물리적 픽셀은 문제가 되지 않기 때문입니다.
픽셀 맞추기
단위 변환 프로세스에서 요소를 물리적 픽셀과 정렬하는 부분을 '픽셀 맞추기'라고 하며, 주석에 표시된 대로 작동합니다. 즉, 소수 픽셀 값을 정수의 실제 픽셀 값에 맞춥니다. 이것이 정확히 어떻게 되는지는 브라우저마다 다릅니다. dPR이 1인 디스플레이에 너비가 791.984px
인 요소가 있는 경우 한 브라우저는 요소를 792px
물리적 픽셀로 렌더링하고 다른 브라우저는 791px
에서 요소를 렌더링할 수 있습니다. 이는 단 하나의 픽셀에 불과하지만, 단일 픽셀은 완벽한 픽셀이어야 하는 렌더링에 해가 될 수 있습니다. 이렇게 하면 흐릿하게 처리되거나 무아레 효과와 같은 아티팩트가 더 많이 보일 수 있습니다.
devicePixelContentBox
devicePixelContentBox
는 기기 픽셀 (즉, 물리적 픽셀) 단위로 된 요소의 콘텐츠 상자를 제공합니다. ResizeObserver
의 일부입니다. Safari 13.1 이후 ResizeObserver는 이제 모든 주요 브라우저에서 지원되지만, devicePixelContentBox
속성은 현재 Chrome 84 이상에서만 사용할 수 있습니다.
ResizeObserver
에서 언급했듯이 이는 요소의 document.onresize
와 같습니다. ResizeObserver
의 콜백 함수는 페인트 전과 레이아웃 후에 호출됩니다. 즉, 콜백의 entries
매개변수에는 페인트 직전에 관찰된 모든 요소의 크기가 포함됩니다. 위에서 설명한 캔버스 문제의 맥락에서 이 기회를 사용하여 캔버스의 픽셀 수를 조정하여 캔버스 픽셀과 실제 픽셀 간에 정확히 일대일로 매핑되도록 할 수 있습니다.
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']});
observer.observe()
의 옵션 객체에 있는 box
속성을 사용하면 관찰하려는 크기를 정의할 수 있습니다. 따라서 각 ResizeObserverEntry
은 항상 borderBoxSize
, contentBoxSize
, devicePixelContentBoxSize
(브라우저에서 지원하는 경우)를 제공하지만 콜백은 관찰된 상자 측정항목 중 하나라도 변경될 때만 호출됩니다.
이 새로운 속성을 사용하면 캔버스의 크기와 위치에 애니메이션을 적용할 수 있으며 (단수 픽셀 값을 효과적으로 보장함) 렌더링 시 모아레 효과는 보지 못합니다. getBoundingClientRect()
를 사용하는 접근 방식에 모아레 효과를 확인하고 새 ResizeObserver
속성으로 어떻게 이를 방지할 수 있는지 알아보려면 Chrome 84 이상에서 데모를 참고하세요.
기능 감지
사용자의 브라우저가 devicePixelContentBox
를 지원하는지 확인하려면 모든 요소를 관찰하고 속성이 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
}
결론
픽셀은 웹에서 놀라울 정도로 복잡한 주제이며 지금까지는 사용자의 화면에서 요소가 차지하는 실제 픽셀 수를 정확히 알 수 있는 방법이 없었습니다. ResizeObserverEntry
의 새로운 devicePixelContentBox
속성은 이러한 정보를 제공하고 <canvas>
를 사용하여 완벽한 픽셀 렌더링을 수행할 수 있게 해 줍니다. devicePixelContentBox
버전은 Chrome 84 이상에서 지원됩니다.