Bir kanvasta gerçekten kaç piksel vardır?
Chrome 84'ten beri ResizeObserver, öğenin boyutunu fiziksel piksel cinsinden ölçen devicePixelContentBox
adlı yeni bir kutu ölçümünü destekler. Bu sayede özellikle yüksek yoğunluklu ekranlarda piksel mükemmelliğinde grafikler oluşturulabilir.
Arka plan: CSS pikselleri, kanvas pikselleri ve fiziksel pikseller
Genellikle em
, %
veya vh
gibi soyut uzunluk birimleriyle çalışırız ancak tüm bunlar piksel olarak ifade edilir. CSS'de bir öğenin boyutunu veya konumunu belirttiğimizde, tarayıcıdaki düzen motoru bu değeri piksel (px
) olarak dönüştürür. Bunlar, geçmişi çok eskilere dayanan ve ekranınızdaki piksellerle yalnızca gevşek bir ilişkisi olan "CSS pikselleri"dir.
Uzun bir süre boyunca, ekranın piksel yoğunluğunu 96 DPI ("inç başına nokta") olarak tahmin etmek oldukça makuldü. Bu da herhangi bir monitörün cm başına yaklaşık 38 piksele sahip olacağı anlamına geliyordu. Zaman içinde monitörler büyüdü ve/veya küçüldü ya da aynı yüzey alanında daha fazla piksel içermeye başladı. Buna, web'deki birçok içeriğin yazı tipi boyutları da dahil olmak üzere boyutlarını px
olarak tanımlaması gerçeğini de ekleyin. Sonuç olarak, bu yüksek yoğunluklu ("HiDPI") ekranlarda okunamayan metinler elde ederiz. Tarayıcılarda, bu duruma karşı önlem olarak monitörün gerçek piksel yoğunluğu gizlenir ve kullanıcının 96 DPI ekranı varmış gibi davranılır. CSS'deki px
birimi, bu sanal 96 DPI ekrandaki bir pikselin boyutunu temsil eder. Bu nedenle "CSS pikseli" adını alır. Bu birim yalnızca ölçüm ve konumlandırma için kullanılır. Gerçek oluşturma işleminden önce fiziksel piksellere dönüşüm gerçekleşir.
Bu sanal ekrandan kullanıcının gerçek ekranına nasıl geçebiliriz? Şunu girin: devicePixelRatio
Bu genel değer, tek bir CSS pikseli oluşturmak için kaç fiziksel piksele ihtiyacınız olduğunu belirtir. devicePixelRatio
(dPR) 1
ise yaklaşık 96 DPI'ye sahip bir monitörde çalışıyorsunuz demektir. Retina ekranınız varsa dPR'niz muhtemelen 2
'tür. Telefonlarda 2
, 3
ve hatta 2.65
gibi daha yüksek (ve daha tuhaf) dPR değerleriyle karşılaşmak nadir değildir. Bu değerin tam olduğunu ancak monitörün gerçek DPI değerini belirlemenize izin vermediğini unutmayın. 2
değerine sahip bir dPR, 1 CSS pikseli tam olarak 2 fiziksel pikselle eşleneceği anlamına gelir.
1
…Genişliği 3.440 piksel, ekran alanı ise 79 cm'dir.
Bu da 110 DPI'lik bir çözünürlüğe yol açar. 96'ya yakın ama tam değil.
Bu, çoğu ekranda <div style="width: 1cm; height: 1cm">
boyutunun tam olarak 1 cm olmamasının da nedenidir.
Son olarak, dPR, tarayıcınızın yakınlaştırma özelliğinden de etkilenebilir. Yakınlaştırırsanız tarayıcı, raporlanan dPR'yi artırır ve her şeyin daha büyük oluşturulmasına neden olur. Yakınlaştırma işlemi sırasında DevTools Console'da devicePixelRatio
simgesini işaretlerseniz kesirli değerlerin göründüğünü görebilirsiniz.
<canvas>
öğesini de ekleyelim. width
ve height
özelliklerini kullanarak kanvasın kaç piksel olmasını istediğinizi belirtebilirsiniz. Dolayısıyla <canvas width=40 height=30>
, 40x30 piksel boyutunda bir tuval olur. Ancak bu, 40x30 piksel boyutunda gösterileceği anlamına gelmez. Tuval, varsayılan olarak width
ve height
özelliğini kullanarak kendi boyutunu tanımlar ancak bildiğiniz ve sevdiğiniz tüm CSS özelliklerini kullanarak tuvali istediğiniz şekilde yeniden boyutlandırabilirsiniz. Şimdiye kadar öğrendiklerimiz doğrultusunda, bunun her senaryoda ideal olmayacağını fark etmiş olabilirsiniz. Tuvaldeki bir piksel, birden fazla fiziksel pikseli veya fiziksel bir pikselin yalnızca bir kısmını kaplayabilir. Bu durum, hoş olmayan görsel kusurlara yol açabilir.
Özetlemek gerekirse: Tuval öğeleri, çizebileceğiniz alanı tanımlamak için belirli bir boyuta sahiptir. Kanvas piksellerinin sayısı, CSS piksellerinde belirtilen kanvasın görüntü boyutundan tamamen bağımsızdır. CSS piksellerinin sayısı, fiziksel piksellerin sayısıyla aynı değildir.
Piksel mükemmelliği
Bazı durumlarda, kanvas piksellerinin fiziksel piksellerle tam olarak eşlenmesi istenir. Bu eşleme sağlanırsa "piksel mükemmelliği" olarak adlandırılır. Özellikle alt piksel oluşturma kullanılırken veya grafikler sıkıca hizalanmış, değişen parlaklıkta çizgilerle gösterilirken metnin okunaklı bir şekilde oluşturulması için piksel mükemmelliğinde oluşturma çok önemlidir.
Web'de mümkün olduğunca piksel mükemmelliğine yakın bir kanvas elde etmek için genellikle aşağıdaki yaklaşımı tercih ederiz:
<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>
Dikkatli okuyucular, dPR tam sayı olmadığında ne olacağını merak edebilir. Bu iyi bir soru ve sorunun tam olarak düğüm noktası da burada. Ayrıca, bir öğenin konumunu veya boyutunu yüzde, vh
veya diğer dolaylı değerleri kullanarak belirtirseniz bu değerler kesirli CSS piksel değerlerine dönüştürülebilir. margin-left: 33%
içeren bir öğe şuna benzer bir dikdörtgenle sonuçlanabilir:
CSS pikselleri tamamen sanal olduğundan, teorik olarak piksel kesirleri kullanmakta bir sakınca yoktur. Ancak tarayıcı, fiziksel piksellerle eşlemeyi nasıl belirler? Çünkü kesirli fiziksel pikseller yoktur.
Piksel yakalama
Birim dönüştürme sürecinin, öğeleri fiziksel piksellerle hizalama işlemine "piksel sabitleme" adı verilir. Bu işlem, tam olarak adının belirttiği şeyi yapar: Kesirli piksel değerlerini tam sayı olan fiziksel piksel değerlerine sabitler. Bu işlemin tam olarak nasıl gerçekleştiği tarayıcıdan tarayıcıya değişir. dPR'nin 1 olduğu bir ekranda 791.984px
genişliğinde bir öğe varsa bir tarayıcı öğeyi 792px
fiziksel pikselde, başka bir tarayıcı ise 791px
'de oluşturabilir. Bu yalnızca tek bir piksellik bir sapma olsa da tek bir piksel, piksel mükemmelliğinde olması gereken oluşturma işlemleri için zararlı olabilir. Bu durum bulanıklığa veya hatta Moiré efekti gibi daha belirgin kusurlara neden olabilir.
devicePixelContentBox
devicePixelContentBox
, bir öğenin içerik kutusunu cihaz pikseli (ör. fiziksel piksel) birimlerinde gösterir. ResizeObserver
kapsamındadır. Safari 13.1'den beri ResizeObserver artık tüm büyük tarayıcılarda destekleniyor olsa da devicePixelContentBox
mülkü şu anda yalnızca Chrome 84 ve sonraki sürümlerde mevcuttur.
ResizeObserver
: Öğeler için document.onresize
gibi bölümünde belirtildiği gibi, bir ResizeObserver
öğesinin geri çağırma işlevi, boyamadan önce ve düzenden sonra çağrılır. Yani geri çağırma işlevine gönderilen entries
parametresi, gözlemlenen tüm öğelerin boyutlarını boyanmadan hemen önce içerir. Yukarıda özetlenen kanvas sorunumuzla ilgili olarak, bu fırsatı kanvasımızdaki piksel sayısını ayarlamak için kullanabiliriz. Böylece, kanvas pikselleri ile fiziksel pikseller arasında tam bir bire bir eşleme elde edebiliriz.
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()
için seçenekler nesnesinde bulunan box
mülkü, gözlemlemek istediğiniz boyutları tanımlamanıza olanak tanır. Bu nedenle, her ResizeObserverEntry
her zaman borderBoxSize
, contentBoxSize
ve devicePixelContentBoxSize
sağlar (tarayıcının desteklemesi koşuluyla). Ancak geri çağırma işlevi yalnızca gözlenen kutu metriklerinden herhangi biri değişirse çağrılır.
Bu yeni mülkle, kanvasın boyutunu ve konumunu animasyonlu hale getirebilir (kesirli piksel değerlerini etkili bir şekilde garanti edebilir) ve oluşturma işleminde herhangi bir Moiré efekti görmeyiz. getBoundingClientRect()
kullanan yaklaşımda moiré etkisini ve yeni ResizeObserver
mülkünün bu etkiden nasıl kaçınmanıza olanak tanıdığını görmek istiyorsanız Chrome 84 veya sonraki sürümlerdeki demoya göz atın.
Özellik algılama
Kullanıcının tarayıcısında devicePixelContentBox
desteği olup olmadığını kontrol etmek için herhangi bir öğeyi gözlemleyebilir ve mülkün ResizeObserverEntry
'te bulunup bulunmadığını kontrol edebiliriz:
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
}
Sonuç
Web'de pikseller şaşırtıcı derecede karmaşık bir konudur ve bugüne kadar bir öğenin kullanıcının ekranında kapladığı fiziksel piksel sayısını tam olarak bilmenin bir yolu yoktu. ResizeObserverEntry
öğesindeki yeni devicePixelContentBox
mülkü size bu bilgiyi verir ve <canvas>
ile piksel mükemmelliğinde oluşturma yapmanıza olanak tanır. devicePixelContentBox
, Chrome 84 ve sonraki sürümlerde desteklenir.