devicePixelContentBox ile piksel mükemmelliğinde oluşturma

Bir tuvalde gerçekte kaç piksel vardır?

Chrome 84'ten itibaren ResizeObserver, öğenin boyutunu fiziksel piksel cinsinden ölçen devicePixelContentBox adlı yeni bir kutu ölçümünü destekler. Bu, özellikle yüksek yoğunluklu ekranlar bağlamında piksel mükemmelliğinde grafiklerin oluşturulmasını sağlar.

Tarayıcı Desteği

  • 84
  • 84
  • 93
  • x

Arka plan: CSS pikselleri, tuval pikselleri ve fiziksel pikseller

Genellikle em, % veya vh gibi uzunluklu soyut birimlerle çalışsak da tüm bu işlerin tümü piksellere denk gelir. CSS'de bir öğenin boyutunu veya konumunu belirttiğimizde, tarayıcının düzen motoru sonuçta bu değeri piksele (px) dönüştürür. Bunlar, çok sayıda geçmişi olan ve ekranınızdaki piksellerle yalnızca serbest bir ilişkisi olan "CSS Pikselleri"dir.

Uzun bir süre için, bir kişinin ekran piksel yoğunluğunu 96 DPI ("inç başına nokta sayısı") ile tahmin etmek oldukça makul bir ölçüttür. Diğer bir deyişle, herhangi bir monitörün cm başına yaklaşık 38 piksel olması gerekir. Zaman içinde, monitörler büyümüş ve/veya küçülmeye ya da aynı yüzey alanında daha fazla piksele sahip olmaya başlamıştır. Bu durumu, px yazı tipi boyutu da dahil olmak üzere web'deki pek çok içeriğin boyutlarının tanımlandığı ve bu yüksek yoğunluklu ("HiDPI") ekranlarda okunaksız metinlerle karşılaştığımız gerçeği de hesaba katarız. Buna karşı bir önlem olarak tarayıcılar ekranın gerçek piksel yoğunluğunu gizler ve bunun yerine kullanıcının ekranı 96 DPI olduğunu varsayar. CSS'deki px birimi, 96 DPI sanal ekranda bir pikselin boyutunu temsil eder. Bu nedenle "CSS Pikseli" adı verilir. Bu birim yalnızca ölçüm ve konumlandırma için kullanılır. Gerçek oluşturma 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) değeri 1 ise yaklaşık 96 DPI'ya sahip bir monitörle çalışıyorsunuz demektir. Retina ekranınız varsa dPR'niz muhtemelen 2 olur. Telefonlarda 2, 3, hatta 2.65 gibi daha yüksek (ve daha tuhaf) dPR değerleriyle karşılaşmak olağan dışı bir durumdur. Bu değerin tam olduğunu unutmamak gerekir, ancak monitörün gerçek DPI değerini elde etmenize izin vermez. 2 dPR'si, 1 CSS pikselinin tam olarak 2 fiziksel pikselle eşleneceği anlamına gelir.

Örnek
Chrome'a göre monitörümdeki dPR: 1

Ekran genişliği 3.440 piksel, görüntüleme alanı ise 79 cm genişliğinde. Böylece 110 DPI çözünürlük elde edilir. 96'ya yakın ama doğru değil. <div style="width: 1cm; height: 1cm"> cihazının çoğu ekranda tam olarak 1 cm ölçülmemesinin 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 olmasına neden olur. Yakınlaştırırken Geliştirici Araçları Konsolu'nda devicePixelRatio kutusunu işaretlerseniz kesirli değerler görebilirsiniz.

Geliştirici Araçları, yakınlaştırma nedeniyle çeşitli kesirli devicePixelRatio değerleri gösteriyor.

<canvas> öğesini karışıma ekleyelim. width ve height özelliklerini kullanarak kanvasın kaç piksele sahip olmasını istediğinizi belirtebilirsiniz. <canvas width=40 height=30>, 40x30 piksellik bir tuval olur. Ancak bu, reklamın 40x30 piksel boyutunda gösterileceği anlamına gelmez. Varsayılan olarak, kanvas, içsel boyutunu tanımlamak için width ve height özelliklerini kullanır. Ancak bildiğiniz ve sevdiğiniz tüm CSS özelliklerini kullanarak kanvası dilediğiniz şekilde yeniden boyutlandırabilirsiniz. Şu ana kadar öğrendiklerimizi dikkate alarak, bunun her senaryo için ideal olmayabileceğini düşünebilirsiniz. Kanvastaki bir piksel, birden çok fiziksel pikseli veya bir fiziksel 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 pikseli olarak belirtilen kanvasın ekran boyutundan tamamen bağımsızdır. CSS pikseli sayısı, fiziksel piksel sayısı ile aynı değildir.

Pixel mükemmelliği

Bazı senaryolarda, tuval piksellerinden fiziksel piksellere tam bir eşlemenin olması istenir. Bu eşleme gerçekleştirilirse, bu "piksel-mükemmel" olarak adlandırılır. Piksel mükemmelliği oluşturma, özellikle alt piksel oluşturma kullanılırken veya grafiklerin dönüşümlü parlaklıkta iyi bir şekilde hizalı satırlarıyla görüntülendiği durumlarda metnin okunabilir şekilde oluşturulması için büyük önem taşır.

Web'de mükemmel piksel kalitesine sahip bir tuvale olabildiğince yakın bir sonuç elde etmek için en çok tercih edilen yaklaşım bu oldu:

<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ı okuyucu, dPR tam sayı değeri olmadığında ne olacağını merak ediyor olabilir. Bu güzel bir soru ve tüm bu sorunun en önemli noktasının tam olarak ne olduğu merak ediliyor. Ayrıca bir öğenin konumunu veya boyutunu yüzde, vh ya da diğer dolaylı değerleri kullanarak belirtirseniz bunların CSS piksel değerlerine çözümlenmesi mümkündür. margin-left: 33% değerine sahip bir öğe, şunun gibi bir dikdörtgene sahip olabilir:

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

CSS pikselleri tamamen sanaldır, dolayısıyla teoride pikselin kesirli olması uygundur, ancak tarayıcı fiziksel piksellerle eşlemeyi nasıl çözer? Çünkü kesirli fiziksel pikseller bir şey değildir.

Piksel tutturma

Birim dönüştürme işleminin, öğelerin fiziksel piksellerle hizalanmasını sağlayan kısmına "piksel tutturma" adı verilir ve kutu üzerinde yaptığı işlem yapılır: Kesirli piksel değerlerini tam sayı, fiziksel piksel değerlerine tutturur. Bunun tam olarak nasıl gerçekleştiği tarayıcıdan tarayıcıya farklılık gösterir. dPR'nin 1 olduğu bir ekranda 791.984px genişliğinde bir öğemiz varsa bir tarayıcı bu öğeyi 792px fiziksel pikselde, başka bir tarayıcı ise 791px değerinde oluşturabilir. Bu durumda sadece tek bir piksel eksik, ancak tek bir piksel mükemmel piksel olması gereken oluşturmalar için zararlı olabilir. Bu durum, bulanıklığa veya Moiré efekti gibi daha görünür kusurlara neden olabilir.

Üstteki resim, farklı renkteki piksellerden oluşan bir kafestir. Alttaki resim yukarıdakiyle aynıdır, ancak genişlik ve yükseklik iki doğrusal ölçeklendirme kullanılarak bir piksel azaltılmıştır. Ortaya çıkan desene Moiré efekti denir.
(Resmi herhangi bir ölçek uygulamadan görmek için bu resmi yeni bir sekmede açmanız gerekebilir.)

devicePixelContentBox

devicePixelContentBox, bir öğenin içerik kutusunu cihaz pikseli (yani fiziksel piksel) birimlerinde verir. Bu, ResizeObserver kapsamında. Safari 13.1'den bu yana DimensionObserver tüm önemli tarayıcılarda desteklenmektedir. Bununla birlikte, devicePixelContentBox özelliği şimdilik yalnızca Chrome 84 ve sonraki sürümlerde kullanılmaktadır.

ResizeObserver bölümünde belirtildiği gibi: Öğeler için document.onresize işlevine benzerdir. ResizeObserver öğesinin geri çağırma işlevi, boyama öncesinde ve düzenden sonra çağrılacaktır. Bu, geri çağırmaya ilişkin entries parametresinin, gözlemlenen tüm öğelerin boyutlarını boyanmadan hemen önce içereceği anlamına gelir. Yukarıda özetlenen tuval sorunumuz bağlamında, bu fırsatı tuvalimizdeki piksel sayısını ayarlamak için kullanabiliriz. Böylece, tuval pikselleri ile fiziksel pikseller arasında tam bire bir eşleme yapabiliyoruz.

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. Dolayısıyla her ResizeObserverEntry her zaman borderBoxSize, contentBoxSize ve devicePixelContentBoxSize sağlar (tarayıcının desteklemesi koşuluyla) geri çağırma yalnızca gözlemlenen kutu metriklerinden herhangi biri değişirse çağrılır.

Bu yeni özellik sayesinde kanvasımızın boyutunu ve konumunu bile canlandırabilir (kesirli piksel değerlerini etkili bir şekilde garanti eder) ve oluşturma üzerinde Moiré etkisi görmeyebiliriz. Moiré'nin getBoundingClientRect() kullanarak yaklaşım üzerindeki etkisini ve yeni ResizeObserver özelliğinin bunu önlemenize nasıl olanak tanıdığını görmek isterseniz Chrome 84 veya sonraki sürümlerdeki demoya göz atın.

Özellik algılama

Bir kullanıcının tarayıcısının devicePixelContentBox desteği olup olmadığını kontrol etmek için herhangi bir öğeyi gözlemleyip özelliğin ResizeObserverEntry üzerinde 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ç

Pikseller web'de şaşırtıcı derecede karmaşık bir konudur ve şimdiye 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, bu bilgiyi verir ve <canvas> ile mükemmel piksel oluşturmalar yapmanızı sağlar. devicePixelContentBox, Chrome 84 ve sonraki sürümlerde desteklenir.