devicePixelContentBox के साथ Pixel-परफ़ेक्ट रेंडरिंग

किसी कैनवस में वाकई कितने पिक्सल होते हैं?

Chrome 84 के बाद के वर्शन में, ResizeObserver काम करता है. यह devicePixelContentBox नाम के एक नए बॉक्स मेज़रमेंट के साथ काम करता है. यह एलिमेंट के डाइमेंशन को फ़िज़िकल पिक्सल में मापता है. इससे, खास तौर पर ज़्यादा डेंसिटी वाली स्क्रीन के लिए, पिक्सल-परफ़ेक्ट ग्राफ़िक को रेंडर किया जा सकता है.

ब्राउज़र सहायता

  • 84
  • 84
  • 93
  • x

बैकग्राउंड: सीएसएस पिक्सल, कैनवस पिक्सल, और फ़िज़िकल पिक्सल

हम अक्सर em, % या vh जैसी लंबाई की ऐब्स्ट्रैक्ट इकाइयों के साथ काम करते हैं, लेकिन ये सभी पिक्सल में बदल जाती हैं. जब भी हम सीएसएस में किसी एलिमेंट का साइज़ या पोज़िशन तय करते हैं, तो ब्राउज़र का लेआउट इंजन उस वैल्यू को पिक्सल (px) में बदल देता है. ये "सीएसएस पिक्सल" होते हैं, जिनका बहुत इतिहास होता है और आपकी स्क्रीन पर मौजूद पिक्सल के साथ सिर्फ़ उनका संबंध टूटता है.

लंबे समय तक, 96DPI ("बिंदु प्रति इंच") के साथ किसी की स्क्रीन की पिक्सल सघनता का अनुमान लगाना काफ़ी उचित था. इसका मतलब है कि किसी भी मॉनिटर में करीब 38 पिक्सल प्रति सें॰मी॰ होंगे. समय के साथ, मॉनिटर बढ़े और/या छोटे हो गए हैं या एक ही सतह पर ज़्यादा पिक्सल होने लगे हैं. इसे इस तथ्य के साथ जोड़ें कि वेब पर मौजूद ज़्यादातर कॉन्टेंट, px में फ़ॉन्ट साइज़ के साथ-साथ अपने डाइमेंशन तय करता है. साथ ही, हमें इन हाई डेंसिटी ("HiDPI") स्क्रीन पर पढ़ा नहीं जा सकने वाला टेक्स्ट मिलता है. काउंटर-मेज़रमेंट के तौर पर, ब्राउज़र मॉनिटर की असल पिक्सल सघनता को छिपा देते हैं और इसके बजाय, उपयोगकर्ता के पास 96 डीपीआई डिस्प्ले होने का दिखावा करते हैं. सीएसएस की px यूनिट, इस वर्चुअल 96 डीपीआई डिसप्ले पर एक पिक्सल का साइज़ दिखाती है. इसलिए, इसे "सीएसएस पिक्सल" नाम दिया गया है. इस इकाई का इस्तेमाल सिर्फ़ मेज़रमेंट और पोज़िशनिंग के लिए किया जाता है. किसी भी तरह की इमेज को रेंडर करने से पहले, उसे फ़िज़िकल पिक्सल में बदल दिया जाता है.

हम इस वर्चुअल डिसप्ले से, उपयोगकर्ता के असली डिसप्ले पर कैसे जा सकते हैं? devicePixelRatio डालें. इस ग्लोबल वैल्यू से आपको यह पता चलता है कि एक सीएसएस पिक्सल बनाने के लिए आपको कितने फ़िज़िकल पिक्सल की ज़रूरत होती है. अगर devicePixelRatio (dPR) 1 है, तो इसका मतलब है कि आप करीब 96DPI वाले मॉनिटर पर काम कर रहे हैं. अगर आपके पास रेटिना स्क्रीन है, तो शायद आपका डीपीआर 2 है. फ़ोन पर 2, 3 या 2.65 जैसे ज़्यादा (और ज़्यादा अजीब) डीपीआर मान आम तौर पर नहीं दिखते हैं. ध्यान रखें कि यह वैल्यू एग्ज़ैक्ट है. हालांकि, इससे आपको मॉनिटर की असल डीपीआई वैल्यू नहीं मिल पाती. 2 के डीपीआर का मतलब है कि एक सीएसएस पिक्सल, बिलकुल 2 फ़िज़िकल पिक्सल को मैप करेगा.

उदाहरण
Chrome के मुताबिक, मेरे मॉनिटर के पास 1 का डीपीआर है...

इसकी चौड़ाई 3440 पिक्सल है और डिसप्ले एरिया 79 सें॰मी॰ चौड़ा है. इससे 110 डीपीआई का रिज़ॉल्यूशन होता है. 96 के करीब है, लेकिन बहुत ज़्यादा नहीं. इसी वजह से, ज़्यादातर डिसप्ले पर <div style="width: 1cm; height: 1cm"> का साइज़ 1 सें॰मी॰ नहीं होगा.

आख़िर में, dPR आपके ब्राउज़र की ज़ूम सुविधा से भी प्रभावित हो सकता है. अगर ज़ूम इन किया जाता है, तो ब्राउज़र रिपोर्ट किए गए डीपीआर को बढ़ा देता है. इससे हर चीज़ की स्क्रीन पर रेंडर होने में ज़्यादा समय लगता है. अगर ज़ूम करते समय DevTools कंसोल में devicePixelRatio देखा जाता है, तो आपको फ़्रैक्शनल वैल्यू दिख सकती हैं.

ज़ूम करने की वजह से, DevTools devicePixelRatio के कुछ ही हिस्से दिख रहे हैं.

चलिए, मिक्स में <canvas> एलिमेंट जोड़ते हैं. width और height एट्रिब्यूट का इस्तेमाल करके, यह तय किया जा सकता है कि कैनवस में कितने पिक्सल होने चाहिए. इस तरह, <canvas width=40 height=30> 40 x 30 पिक्सल वाला कैनवस होगा. हालांकि, इसका मतलब यह नहीं है कि इसे 40 x 30 पिक्सल में दिखाया जाएगा. कैनवस, डिफ़ॉल्ट रूप से width और height एट्रिब्यूट का इस्तेमाल करके, अपना मूल साइज़ तय करेगा. हालांकि, अपनी पसंद और जाने-पहचाने सीएसएस प्रॉपर्टी का इस्तेमाल करके, कैनवस का साइज़ अपने हिसाब से बदला जा सकता है. हमने अब तक जो कुछ भी सीखा है उसके साथ, आपके साथ ऐसा हो सकता है कि हर स्थिति में यह तरीका सही न हो. ऐसा हो सकता है कि कैनवस का एक पिक्सल, कई पिक्सल या सिर्फ़ कुछ पिक्सल को कवर करे. इसकी वजह से विज़ुअल आर्टफ़ैक्ट खराब हो सकते हैं.

खास जानकारी देने के लिए: कैनवस एलिमेंट में एक साइज़ तय होता है, ताकि वह एरिया तय कर सके जिस पर ड्रॉ किया जा सकता है. कैनवस पिक्सल की संख्या, सीएसएस पिक्सल में दिए गए कैनवस के डिसप्ले साइज़ से पूरी तरह अलग है. सीएसएस पिक्सल की संख्या, फ़िज़िकल पिक्सल की संख्या के बराबर नहीं होती.

पिक्सल में सुधार

कुछ स्थितियों में, कैनवस पिक्सल से फ़िज़िकल पिक्सल में सटीक मैपिंग करना ज़रूरी होता है. अगर यह मैपिंग पूरी हो जाती है, तो इसे "पिक्सल-परफ़ेक्ट" कहा जाता है. टेक्स्ट को पढ़ने लायक बनाने के लिए, पिक्सल-परफ़ेक्ट रेंडरिंग ज़रूरी है. खास तौर पर, सब-पिक्सल रेंडरिंग का इस्तेमाल करते समय या ग्राफ़िक में स्क्रीन की चमक को कम या ज़्यादा करने के लिए.

वेब पर पिक्सल-परफ़ेक्ट कैनवस के बहुत पास जाकर कोई लक्ष्य हासिल करने के लिए, इसका इस्तेमाल कुछ हद तक करने के लिए किया जाता है:

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

कोई कुशल पाठक सोच रहा होगा कि जब dPR एक पूर्णांक मान नहीं है, तो क्या होता है. यह एक अच्छा सवाल है और असल में इस समस्या की वजह क्या है. इसके अलावा, अगर प्रतिशत, vh या सीधे पता न चलने वाली अन्य वैल्यू का इस्तेमाल करके, एलिमेंट की पोज़िशन या साइज़ तय किया जाता है, तो हो सकता है कि वे फ़्रैक्शनल सीएसएस पिक्सल वैल्यू में बदल जाएं. margin-left: 33% वाला कोई एलिमेंट, इस तरह के रेक्टैंगल के साथ बन सकता है:

getBoundingClientRect() कॉल के बाद, DevTools पिक्सल वैल्यू अलग-अलग दिख रही हैं.

CSS पिक्सल पूरी तरह से वर्चुअल होते हैं, इसलिए पिक्सल के कुछ हिस्सों का होना तो ठीक है, लेकिन ब्राउज़र फ़िज़िकल पिक्सल के लिए मैपिंग का पता कैसे लगाता है? क्योंकि फ़्रैक्शनल फ़िज़िकल पिक्सल कोई चीज़ नहीं होती.

Pixel स्नैप करना

यूनिट बदलने की प्रक्रिया में, फ़िज़िकल पिक्सल के साथ एलिमेंट को अलाइन करने का काम "पिक्सल स्नैपिंग" कहते हैं. यह टिन पर लिखे गए फ़्रैक्शनल पिक्सल वैल्यू को पूर्णांक, फ़िज़िकल पिक्सल वैल्यू में बदल देता है. यह अलग-अलग ब्राउज़र के हिसाब से कैसे अलग होता है. अगर हमारे पास ऐसे डिसप्ले पर 791.984px की चौड़ाई वाला कोई एलिमेंट है जहां डीपीआर 1 है, तो हो सकता है कि एक ब्राउज़र, एलिमेंट को 792px के फ़िज़िकल पिक्सल पर रेंडर करे, जबकि दूसरा ब्राउज़र उसे 791px पर रेंडर करे. ऐसा करने से सिर्फ़ एक पिक्सल बंद हो सकता है, लेकिन एक पिक्सल से उस रेंडरिंग पर बुरा असर पड़ सकता है जिसे पिक्सल-परफ़ेक्ट होना चाहिए. इस वजह से फ़ोटो धुंधली हो सकती हैं या मॉयर इफ़ेक्ट जैसी चीज़ें ज़्यादा साफ़ दिख सकती हैं.

सबसे ऊपर वाली इमेज में अलग-अलग रंग के पिक्सल का रास्टर है. नीचे की इमेज भी ऊपर दी गई इमेज के जैसी ही है, लेकिन इसकी चौड़ाई और ऊंचाई को बिलीलाइनर स्केलिंग का इस्तेमाल करके एक पिक्सल कम कर दिया गया है. इस उभरते पैटर्न को मॉयर इफ़ेक्ट कहा जाता है.
(इस इमेज पर कोई स्केल लागू न करके, इसे देखने के लिए आपको इसे एक नए टैब में खोलना पड़ सकता है.)

devicePixelContentBox

devicePixelContentBox, आपको डिवाइस पिक्सल (जैसे कि फ़िज़िकल पिक्सल) यूनिट में एलिमेंट का कॉन्टेंट बॉक्स देता है. यह ResizeObserver का हिस्सा है. हालांकि, Safari 13.1 के बाद से अब तक, Resize टॉप की सुविधा सभी ब्राउज़र पर काम करती है. फ़िलहाल, devicePixelContentBox प्रॉपर्टी सिर्फ़ Chrome 84 और उसके बाद के वर्शन में उपलब्ध है.

जैसा कि ResizeObserver में बताया गया है: यह एलिमेंट के लिए document.onresize जैसा है. ResizeObserver के कॉलबैक फ़ंक्शन को पेंट से पहले और लेआउट के बाद कॉल किया जाएगा. इसका मतलब है कि कॉलबैक के लिए entries पैरामीटर में, पेंट किए जाने से ठीक पहले देखे गए सभी एलिमेंट के साइज़ शामिल होंगे. ऊपर बताई गई कैनवस से जुड़ी समस्या के संदर्भ में, हम इस अवसर का इस्तेमाल अपने कैनवस पर पिक्सल की संख्या को अडजस्ट करने के लिए कर सकते हैं. इससे यह पक्का होता है कि हमारे कैनवस पिक्सल और फ़िज़िकल पिक्सल के बीच वन-टू-वन मैपिंग हो जाए.

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() के लिए विकल्प ऑब्जेक्ट में box प्रॉपर्टी की मदद से, यह तय किया जा सकता है कि किन साइज़ को निगरानी करना है. इसलिए, हर ResizeObserverEntry हमेशा borderBoxSize, contentBoxSize, और devicePixelContentBoxSize उपलब्ध कराएगा, बशर्ते ब्राउज़र उसके साथ काम करता हो. कॉलबैक को सिर्फ़ तब शुरू किया जाएगा, जब निगरानी में रखे गए बॉक्स की किसी मेट्रिक में बदलाव होगा.

इस नई प्रॉपर्टी की मदद से, हम अपने कैनवस के साइज़ और उसकी जगह को ऐनिमेट भी कर सकते हैं. इससे, फ़्रैक्शनल पिक्सल वैल्यू की गारंटी मिलती है. साथ ही, रेंडरिंग पर Moiré असर नहीं पड़ता. अगर आपको getBoundingClientRect() का इस्तेमाल करके, मॉयर के असर के बारे में जानना है और यह जानना है कि नई ResizeObserver प्रॉपर्टी का इस्तेमाल करके, इस समस्या से कैसे बचा जा सकता है, तो Chrome 84 या इसके बाद के वर्शन में डेमो पर जाएं!

सुविधा की पहचान करने की सुविधा

यह देखने के लिए कि उपयोगकर्ता के ब्राउज़र में devicePixelContentBox काम करता है या नहीं, हम किसी एलिमेंट का पता लगाते हैं और यह देखते हैं कि ResizeObserverEntry पर प्रॉपर्टी मौजूद है या नहीं:

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
}

नतीजा

Pixel वेब पर एक हैरान करने वाला जटिल विषय है. अब तक आपके पास यह जानने का कोई तरीका नहीं था कि उपयोगकर्ता की स्क्रीन पर किसी एलिमेंट के कितने फ़िज़िकल पिक्सल मौजूद हैं. ResizeObserverEntry पर नई devicePixelContentBox प्रॉपर्टी से आपको ऐसी जानकारी मिलती है. साथ ही, <canvas> की मदद से पिक्सल-परफ़ेक्ट रेंडरिंग भी की जा सकती है. devicePixelContentBox, Chrome 84 के बाद के वर्शन पर काम करता है.