devicePixelContentBox ile piksel mükemmelliğinde oluşturma

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.

Tarayıcı Desteği

  • Chrome: 84.
  • Kenar: 84.
  • Firefox: 93.
  • Safari: desteklenmez.

Kaynak

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.

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.

Yakınlaştırma nedeniyle DevTools'ta çeşitli kesirli devicePixelRatio değerleri gösteriliyor.

<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:

Bir getBoundingClientRect() çağrısının sonucu olarak kesirli piksel değerleri gösteren Geliştirici Araçları.

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.

Üst resim, farklı renkteki piksellerden oluşan bir kafestir. Alttaki resim yukarıdaki resimle aynıdır ancak genişlik ve yükseklik, iki boyutlu ölçeklendirme kullanılarak bir piksel azaltılmıştır. Yeni ortaya çıkan desene Moiré efekti adı verilir.
(Herhangi bir ölçeklendirme uygulanmadan görmek için resmi yeni bir sekmede açmanız gerekebilir.)

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.