devicePixelContentBox ile piksel mükemmelliğinde oluşturma

Bir tuvalde gerçekten kaç piksel var?

Chrome 84'ten itibaren ResizeObserver, devicePixelContentBox adlı yeni bir kutu ölçümünü destekler. Bu ölçüm, öğenin boyutunu fiziksel piksellerle ölçer. Bu sayede, özellikle yüksek yoğunluklu ekranlarda piksel açısından mükemmel grafikler oluşturulabilir.

Browser Support

  • Chrome: 84.
  • Edge: 84.
  • Firefox: 93.
  • Safari: not supported.

Source

Arka plan: CSS pikselleri, tuval pikselleri ve fiziksel pikseller

Genellikle em, % veya vh gibi soyut uzunluk birimleriyle çalışsak da her şey piksellere bağlıdır. CSS'de bir öğenin boyutunu veya konumunu her belirttiğimizde tarayıcının düzen motoru bu değeri sonunda piksele (px) dönüştürür. Bunlar, çok geçmişi olan ve ekranınızdaki piksellerle yalnızca gevşek bir ilişkisi olan "CSS pikselleri"dir.

Uzun süre boyunca, herkesin ekran piksel yoğunluğunu 96 DPI ("inç başına nokta sayısı") ile tahmin etmek oldukça makul bir yaklaşımdı. Bu, herhangi bir monitörün kabaca santimetre başına 38 piksele sahip olacağı anlamına geliyordu. Zamanla monitörler büyüdü ve/veya küçüldü ya da aynı yüzey alanında daha fazla piksel olmaya başladı. Buna, web'deki birçok içeriğin yazı boyutları da dahil olmak üzere boyutlarını px cinsinden tanımladığı gerçeğini de eklediğimizde bu yüksek yoğunluklu ("HiDPI") ekranlarda okunaksız metinler ortaya çıkıyor. Tarayıcılar, karşı önlem olarak monitörün gerçek piksel yoğunluğunu gizler ve bunun yerine kullanıcının 96 DPI ekranı olduğunu varsayar. CSS'deki px birimi, bu sanal 96 DPI ekranda bir pikselin boyutunu temsil eder. Bu nedenle, "CSS Pikseli" olarak adlandırılı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üştürme işlemi yapılır.

Bu sanal ekrandan kullanıcının gerçek ekranına nasıl geçiş yaparız? Şunu girin: devicePixelRatio Bu genel değer, tek bir CSS pikseli oluşturmak için kaç tane fiziksel piksel gerektiğini gösterir. devicePixelRatio (dPR) 1 ise yaklaşık 96 DPI'lık bir monitörde çalışıyorsunuz demektir. Retina ekranınız varsa dPR'niz muhtemelen 2'dır. Telefonlarda 2, 3 veya hatta 2.65 gibi daha yüksek (ve daha garip) dPR değerleriyle karşılaşmak yaygın bir durumdur. Bu değerin kesin olduğunu ancak monitörün gerçek DPI değerini elde etmenize izin vermediğini unutmayın. 2 dPR, 1 CSS pikselinin tam olarak 2 fiziksel piksele eşleneceği anlamına gelir.

Örnek
Chrome'a göre monitörümün dPR değeri 1.

Genişliği 3.440 pikseldir ve ekran alanı 79 cm genişliğindedir. Bu da 110 DPI çözünürlüğe yol açar. 96'ya yakın ama tam değil. Bu nedenle, çoğu ekranda <div style="width: 1cm; height: 1cm"> 1 cm boyutunda ölçülmez.

Son olarak, dPR tarayıcınızın yakınlaştırma özelliğinden de etkilenebilir. Yakınlaştırdığınızda tarayıcı, bildirilen dPR'yi artırır ve her şeyin daha büyük oluşturulmasına neden olur. Yakınlaştırırken bir DevTools Console'da devicePixelRatio işaretini koyarsanız kesirli değerlerin göründüğünü görebilirsiniz.

Yakınlaştırma nedeniyle çeşitli kesirli devicePixelRatio gösteren Geliştirici Araçları.

<canvas> öğesini de ekleyelim. width ve height özelliklerini kullanarak tuvalin kaç piksel olmasını istediğinizi belirtebilirsiniz. Bu nedenle <canvas width=40 height=30>, 40 x 30 piksellik bir tuval olur. Ancak bu, 40 x 30 piksel boyutunda gösterileceği anlamına gelmez. Tuval, varsayılan olarak kendi boyutunu tanımlamak için width ve height özelliklerini kullanır ancak tuvali, bildiğiniz ve sevdiğiniz tüm CSS özelliklerini kullanarak rastgele yeniden boyutlandırabilirsiniz. Şimdiye kadar öğrendiklerimizden, bu yöntemin her senaryo için ideal olmayabileceğini anlamış olabilirsiniz. Tuvaldeki bir piksel, birden fazla fiziksel pikseli veya fiziksel pikselin yalnızca bir kısmını kaplayabilir. Bu durum, görsel olarak hoş olmayan eserlere yol açabilir.

Özetlemek gerekirse: Tuval öğeleri, üzerinde çizim yapabileceğiniz alanı tanımlamak için belirli bir boyuta sahiptir. Tuval piksellerinin sayısı, CSS pikselleriyle belirtilen tuvalin görüntüleme 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 piksellerinin fiziksel piksellerle tam olarak eşlenmesi istenir. Bu eşleme gerçekleştirilirse "piksel mükemmelliği" olarak adlandırılır. Metnin okunabilir şekilde oluşturulması için piksel düzeyinde mükemmel oluşturma çok önemlidir. Bu durum, özellikle alt piksel oluşturma kullanılırken veya alternatif parlaklıkta sıkıca hizalanmış çizgiler içeren grafikler görüntülenirken geçerlidir.

Web'de piksel açısından mükemmel bir tuvale olabildiğince yakın bir sonuç elde etmek için şu yaklaşım kullanılmıştır:

<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ı değeri olmadığında ne olacağını merak edebilir. Bu iyi bir soru ve tüm bu sorunun asıl kaynağı tam olarak burada yatıyor. Ayrıca, bir öğenin konumunu veya boyutunu yüzde, vh ya da diğer dolaylı değerleri kullanarak belirtirseniz bu değerler kesirli CSS piksel değerlerine dönüşebilir. margin-left: 33% içeren bir öğe şu şekilde bir dikdörtgenle sonuçlanabilir:

Geliştirici Araçları, getBoundingClientRect() çağrısı sonucunda kesirli piksel değerlerini gösteriyor.

CSS pikselleri tamamen sanal olduğundan, teoride pikselin kesirli kısımlarının olması sorun değildir. Ancak tarayıcı, fiziksel piksellerle eşlemeyi nasıl belirler? Çünkü kesirli fiziksel pikseller diye bir şey yoktur.

Piksele tutturma

Birim dönüştürme sürecinin, öğeleri fiziksel piksellerle hizalamayı sağlayan kısmına "piksel yapıştırma" denir. Bu işlem, piksel değerlerini tam sayı olan fiziksel piksel değerlerine yapıştırır. Bu durumun 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 genişliği 791.984px olan bir öğemiz varsa bir tarayıcı bu öğeyi 792px fiziksel pikselde, başka bir tarayıcı ise 791px fiziksel pikselde oluşturabilir. Bu, yalnızca tek bir pikselin eksik olmasıdır ancak tek bir piksel, mükemmel piksel gerektiren oluşturma işlemleri için zararlı olabilir. Bu durum, bulanıklığa veya Moiré efekti gibi daha belirgin yapaylıklara yol açabilir.

Üstteki resim, farklı renklerdeki piksellerin raster görüntüsüdür. Alttaki resim, yukarıdakiyle aynıdır ancak genişlik ve yükseklik, çift doğrusal ölçeklendirme kullanılarak bir piksel azaltılmıştır. Ortaya çıkan bu desene Moiré efekti denir.
(Bu resmi, ölçekleme uygulanmadan görmek için yeni bir sekmede açmanız gerekebilir.)

devicePixelContentBox

devicePixelContentBox, cihaz pikseli (yani fiziksel piksel) birimlerinde bir öğenin içerik kutusunu verir. ResizeObserver'nın bir parçasıdır. ResizeObserver, Safari 13.1'den beri tüm büyük tarayıcılarda destekleniyor olsa da devicePixelContentBox özelliği şu anda yalnızca Chrome 84 ve sonraki sürümlerde kullanılabiliyor.

ResizeObserver: it's like document.onresize for elements başlıklı makalede belirtildiği gibi, ResizeObserver öğesinin geri çağırma işlevi, boyamadan önce ve düzenden sonra çağrılır. Bu durumda, geri çağırma işlevine iletilen entries parametresi, boyanmadan hemen önce gözlemlenen tüm öğelerin boyutlarını içerir. Yukarıda özetlenen kanvas sorunumuz bağlamında, bu fırsatı kanvasımızdaki piksel sayısını ayarlamak için kullanabiliriz. Böylece, kanvas pikselleri ile fiziksel pikseller arasında 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 nesnesindeki box özelliği, hangi boyutları gözlemlemek istediğinizi tanımlamanıza olanak tanır. Dolayısıyla her ResizeObserverEntry her zaman borderBoxSize, contentBoxSize ve devicePixelContentBoxSize değerlerini sağlasa da (tarayıcı desteklediği sürece) geri çağırma yalnızca gözlemlenen kutu metriklerinden herhangi biri değişirse çağrılır.

Bu yeni özellik sayesinde, tuvalimizin boyutunu ve konumunu animasyonla değiştirebiliriz (kesirli piksel değerlerini etkili bir şekilde garanti eder) ve oluşturmada herhangi bir Moiré efekti görmeyiz. getBoundingClientRect() kullanılarak yapılan yaklaşımda Moiré etkisini ve yeni ResizeObserver özelliğinin bu etkiden nasıl kaçınmanızı sağladığını görmek istiyorsanız Chrome 84 veya sonraki sürümlerdeki demoya göz atın.

Özellik algılama

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

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 size verir ve <canvas> ile piksel düzeyinde mükemmel oluşturmalar yapmanıza olanak tanır. devicePixelContentBox, Chrome 84 ve sonraki sürümlerinde desteklenir.