Berapa banyak piksel yang sebenarnya ada di kanvas?
Sejak Chrome 84, ResizeObserver mendukung pengukuran kotak baru yang disebut devicePixelContentBox
, yang mengukur dimensi elemen dalam piksel fisik. Hal ini memungkinkan rendering grafis yang sempurna piksel, terutama dalam konteks layar dengan kepadatan tinggi.
Latar belakang: Piksel CSS, piksel kanvas, dan piksel fisik
Meskipun kita sering menggunakan unit panjang abstrak seperti em
, %
, atau vh
, semuanya bermuara pada piksel. Setiap kali kita menentukan ukuran atau posisi elemen di CSS, mesin tata letak browser pada akhirnya akan mengonversi nilai tersebut menjadi piksel (px
). Ini adalah "Piksel CSS", yang memiliki banyak histori dan hanya memiliki hubungan longgar dengan piksel yang Anda miliki di layar.
Selama waktu yang lama, estimasi kepadatan piksel layar seseorang dengan 96DPI ("titik per inci") cukup wajar, yang berarti monitor tertentu akan memiliki sekitar 38 piksel per cm. Seiring waktu, monitor bertambah besar dan/atau mengecil atau mulai memiliki lebih banyak piksel di area permukaan yang sama. Gabungkan dengan fakta bahwa banyak konten di web menentukan dimensinya, termasuk ukuran font, di px
, dan kita akan mendapatkan teks yang tidak dapat dibaca di layar berkepadatan tinggi ("HiDPI") ini. Sebagai tindakan pencegahan, browser menyembunyikan kepadatan piksel monitor yang sebenarnya dan berpura-pura bahwa pengguna memiliki layar 96 DPI. Satuan px
di CSS mewakili ukuran satu piksel pada layar 96 DPI virtual ini, sehingga namanya "Piksel CSS". Unit ini hanya digunakan untuk pengukuran dan pemosisian. Sebelum rendering yang sebenarnya terjadi, konversi ke piksel fisik akan terjadi.
Bagaimana cara beralih dari tampilan virtual ini ke tampilan sebenarnya milik pengguna? Masukkan devicePixelRatio
. Nilai global ini memberi tahu Anda jumlah piksel fisik yang diperlukan untuk membentuk satu piksel CSS. Jika devicePixelRatio
(dPR) adalah 1
, Anda sedang menggunakan monitor dengan sekitar 96 DPI. Jika Anda memiliki layar retina, dPR Anda mungkin 2
. Di ponsel, tidak jarang menemukan nilai dPR yang lebih tinggi (dan lebih aneh) seperti 2
, 3
, atau bahkan 2.65
. Perhatikan bahwa nilai ini tepat, tetapi tidak memungkinkan Anda memperoleh nilai DPI aktual monitor. dPR 2
berarti 1 piksel CSS akan dipetakan ke persis 2 piksel fisik.
1
menurut Chrome…Lebarnya 3440 piksel dan area tampilannya 79 cm.
Hal ini menghasilkan resolusi 110 DPI. Mendekati 96, tetapi tidak tepat.
Itulah juga alasan mengapa <div style="width: 1cm; height: 1cm">
tidak akan mengukur ukuran 1 cm secara persis di sebagian besar layar.
Terakhir, dPR juga dapat terpengaruh oleh fitur zoom browser Anda. Jika Anda melakukan zoom in, browser akan meningkatkan dPR yang dilaporkan, sehingga semuanya dirender menjadi lebih besar. Jika Anda memeriksa devicePixelRatio
di Konsol DevTools saat melakukan zoom, Anda dapat melihat nilai pecahan muncul.
Mari tambahkan elemen <canvas>
ke campuran. Anda dapat menentukan jumlah piksel yang diinginkan untuk kanvas menggunakan atribut width
dan height
. Jadi, <canvas width=40 height=30>
akan menjadi kanvas dengan ukuran 40x30 piksel. Namun, hal ini tidak berarti bahwa gambar akan ditampilkan dengan ukuran 40x30 piksel. Secara default, kanvas akan menggunakan atribut width
dan height
untuk menentukan ukuran intrinsiknya, tetapi Anda dapat mengubah ukuran kanvas secara arbitrer menggunakan semua properti CSS yang Anda ketahui dan sukai. Dengan semua yang telah kita pelajari sejauh ini, Anda mungkin berpikir bahwa hal ini tidak akan ideal dalam setiap skenario. Satu piksel di kanvas mungkin akhirnya menutupi beberapa piksel fisik, atau hanya sebagian kecil piksel fisik. Hal ini dapat menyebabkan artefak visual yang tidak menyenangkan.
Untuk meringkas: Elemen kanvas memiliki ukuran tertentu untuk menentukan area yang dapat Anda gambar. Jumlah piksel kanvas sepenuhnya independen dari ukuran tampilan kanvas, yang ditentukan dalam piksel CSS. Jumlah piksel CSS tidak sama dengan jumlah piksel fisik.
Pixel sempurna
Dalam beberapa skenario, sebaiknya Anda memiliki pemetaan yang tepat dari piksel kanvas ke piksel fisik. Jika pemetaan ini tercapai, pemetaan tersebut disebut "sempurna piksel". Rendering sempurna piksel sangat penting untuk rendering teks yang dapat dibaca, terutama saat menggunakan rendering subpiksel atau saat menampilkan grafik dengan garis kecerahan bergantian yang sejajar dengan ketat.
Untuk mendapatkan sesuatu yang sedekat mungkin dengan kanvas yang sempurna piksel di web, ini adalah pendekatan yang paling sering digunakan:
<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>
Pembaca yang jeli mungkin bertanya-tanya apa yang terjadi jika dPR bukan nilai bilangan bulat. Pertanyaan bagus. Inilah inti dari seluruh masalah ini. Selain itu, jika Anda menentukan posisi atau ukuran elemen menggunakan persentase, vh
, atau nilai tidak langsung lainnya, nilai tersebut mungkin akan di-resolve ke nilai piksel CSS pecahan. Elemen dengan margin-left: 33%
dapat berakhir dengan persegi panjang seperti ini:
Piksel CSS sepenuhnya virtual, sehingga memiliki fraksi piksel tidak masalah secara teori, tetapi bagaimana browser mengetahui pemetaan ke piksel fisik? Karena piksel fisik pecahan tidak ada.
Pengambilan piksel
Bagian dari proses konversi satuan yang menangani perataan elemen dengan piksel fisik disebut "pixel snapping", dan melakukan apa yang tertulis di kaleng: Menghubungkan nilai piksel pecahan ke nilai piksel fisik bilangan bulat. Cara terjadinya hal ini berbeda-beda di setiap browser. Jika kita memiliki elemen dengan lebar 791.984px
pada layar dengan dPR 1, satu browser mungkin merender elemen pada piksel fisik 792px
, sementara browser lain mungkin merendernya pada 791px
. Ini hanya satu piksel yang salah, tetapi satu piksel dapat merusak rendering yang harus sempurna untuk piksel. Hal ini dapat menyebabkan pemburaman atau bahkan artefak yang lebih terlihat seperti efek Moiré.
devicePixelContentBox
devicePixelContentBox
memberi Anda kotak konten elemen dalam unit piksel perangkat (yaitu piksel fisik). Ini adalah bagian dari ResizeObserver
. Meskipun ResizeObserver kini didukung di semua browser utama sejak Safari 13.1, properti devicePixelContentBox
hanya ada di Chrome 84+ untuk saat ini.
Seperti yang disebutkan dalam ResizeObserver
: seperti document.onresize
untuk elemen, fungsi callback ResizeObserver
akan dipanggil sebelum proses menggambar dan setelah tata letak. Artinya, parameter entries
ke callback akan berisi ukuran semua elemen yang diamati tepat sebelum digambar. Dalam konteks masalah kanvas yang diuraikan di atas, kita dapat menggunakan peluang ini untuk menyesuaikan jumlah piksel pada kanvas, memastikan bahwa kita mendapatkan pemetaan satu-ke-satu yang tepat antara piksel kanvas dan piksel fisik.
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']});
Properti box
dalam objek opsi untuk observer.observe()
memungkinkan Anda menentukan ukuran yang ingin diamati. Jadi, meskipun setiap ResizeObserverEntry
akan selalu menyediakan borderBoxSize
, contentBoxSize
, dan devicePixelContentBoxSize
(asalkan browser mendukungnya), callback hanya akan dipanggil jika salah satu metrik kotak yang diamati berubah.
Dengan properti baru ini, kita bahkan dapat menganimasikan ukuran dan posisi kanvas (yang secara efektif menjamin nilai piksel pecahan), dan tidak melihat efek Moiré pada rendering. Jika Anda ingin melihat efek Moiré pada pendekatan menggunakan getBoundingClientRect()
, dan cara properti ResizeObserver
baru memungkinkan Anda menghindarinya, lihat demo di Chrome 84 atau yang lebih baru.
Deteksi fitur
Untuk memeriksa apakah browser pengguna memiliki dukungan untuk devicePixelContentBox
, kita dapat mengamati elemen apa pun, dan memeriksa apakah properti tersebut ada di 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
}
Kesimpulan
Piksel adalah topik yang sangat kompleks di web dan hingga saat ini tidak ada cara bagi Anda untuk mengetahui jumlah piksel fisik yang tepat yang digunakan elemen di layar pengguna. Properti devicePixelContentBox
baru di ResizeObserverEntry
memberi Anda informasi tersebut dan memungkinkan Anda melakukan rendering sempurna piksel dengan <canvas>
. devicePixelContentBox
didukung di Chrome 84+.