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, tuval 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ının düzen motoru sonunda bu değeri piksellere (px
) dönüştürür. Bunlar, çok fazla geçmişe sahip ve yalnızca ekranınızdaki piksellerle yaklaşık 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ı. Web'deki birçok içeriğin yazı tipi boyutları da dahil olmak üzere boyutlarının px
olarak tanımlandığını ve bu yüksek yoğunluklu ("HiDPI") ekranlarda metnin okunaklı olmayan metinle sonuçlandığını düşünerek bunu birleştirin. 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 bir oluşturma işlemi gerçekleşmeden ö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 elde etmenize izin vermediğini unutmayın. 2
değerine sahip bir dPR, 1 CSS pikselin tam olarak 2 fiziksel pikselle eşleşeceği anlamına gelir.
1
…Genişliği 3.440 piksel, görüntü alanı ise 79 cm'dir.
Bu da 110 DPI'lik bir çözünürlüğe yol açar. 96’ya yakın ama tam doğru değil.
<div style="width: 1cm; height: 1cm">
değerinin çoğu ekranda tam olarak 1 cm'lik ölçülememesinin nedeni de budur.
Son olarak, dPR tarayıcınızın yakınlaştırma özelliğinden de etkilenebilir. Yakınlaştırırsanız, tarayıcı bildirilen dPR'yi artırarak 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 karışıma 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, öğenin 40x30 pikselde 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. Şu ana kadar öğrendiğimiz tüm unsurları göz önünde bulundurduğumuzda, bunun her senaryoda ideal olmadığını söyleyebiliriz. Tuvaldeki bir piksel, birden fazla fiziksel pikseli veya fiziksel bir pikselin yalnızca bir kısmını kaplayabilir. Bu da hoş olmayan görsel yapılara yol açabilir.
Özetlemek gerekirse: Tuval öğeleri, üzerinde çizim yapabileceğ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.
Pixel mükemmelliği
Bazı senaryolarda, tuval piksellerinden fiziksel piksellere tam bir eşlemenin yapılması tercih edilir. 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 piksel mükemmelliğinde bir tuvale olabildiğince yakın bir şey elde etmek için genel yaklaşım şuydu:
<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>
Akıllı bir okuyucu, dPR'nin tam sayı değeri olmadığında ne olduğunu merak ediyor olabilir. Bu çok iyi bir soru ve bütün bu sorunun temel noktasının tam olarak nerede olduğunu anlamışsınızdır. 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 öğede şöyle bir dikdörtgen elde edilebilir:
CSS pikselleri tamamen sanaldır. Dolayısıyla teoride bir pikselin kesirli olması normaldir. Ancak tarayıcı fiziksel piksellerle eşlemeyi nasıl belirler? Çünkü kesirli fiziksel piksel yoktur.
Piksel tutturma
Birim dönüştürme işleminde öğelerin fiziksel piksellerle hizalanmasını gerektiren bölüme "piksel tutturma" denir ve tenekede söylenenleri yapar: Kesirli piksel değerlerini tam sayı, fiziksel piksel değerlerine tutturur. 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 durumda tek bir piksel daha vardır ancak tek bir piksel mükemmel olması gereken oluşturma işlemlerine zarar verebilir. Bu durum bulanıklığa veya hatta Moiré efekti gibi daha belirgin kusurlara neden olabilir.
devicePixelContentBox
devicePixelContentBox
, bir öğenin içerik kutusunu cihaz pikseli (yani fiziksel piksel) birimleri cinsinden verir. 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 ait 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 ederiz.
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 nesnesindeki box
özelliği, gözlemlemek istediğiniz boyutları tanımlamanıza olanak tanır. Bu nedenle her ResizeObserverEntry
her zaman borderBoxSize
, contentBoxSize
ve devicePixelContentBoxSize
sağlar ancak (tarayıcının desteklemesi koşuluyla) geri çağırma yalnızca gözlemlenen kutu metriklerinden herhangi biri değiştiğinde ç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()
kullanarak yaklaşım üzerindeki Moiré etkisini ve yeni ResizeObserver
özelliğinin bunu önlemenize nasıl olanak sağladığını görmek isterseniz Chrome 84 veya sonraki sürümlerde demoya göz atın.
Özellik algılama
Bir kullanıcının tarayıcısının devicePixelContentBox
özelliğini destekleyip desteklemediğini kontrol etmek için herhangi bir öğeyi gözlemleyip özelliğin ResizeObserverEntry
öğesinde mevcut olup olmadığı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
üzerindeki yeni devicePixelContentBox
özelliği size bu bilgiyi sağlar ve <canvas>
ile mükemmel kalitede oluşturma işlemleri yapmanızı sağlar. devicePixelContentBox
, Chrome 84 ve sonraki sürümlerde desteklenir.