رندر عالی پیکسل با دستگاهPixelContentBox

واقعا چند پیکسل در یک بوم وجود دارد؟

از Chrome 84، ResizeObserver از اندازه‌گیری جعبه جدیدی به نام devicePixelContentBox پشتیبانی می‌کند که ابعاد عنصر را در پیکسل‌های فیزیکی اندازه‌گیری می‌کند. این امکان ارائه گرافیک کامل پیکسلی را به خصوص در زمینه صفحه نمایش های با چگالی بالا فراهم می کند.

پشتیبانی مرورگر

  • 84
  • 84
  • 93
  • ایکس

پس زمینه: پیکسل های CSS، پیکسل های بوم، و پیکسل های فیزیکی

در حالی که ما اغلب با واحدهای انتزاعی طول مانند em ، % یا vh کار می کنیم، همه اینها به پیکسل خلاصه می شود. هر زمان که اندازه یا موقعیت یک عنصر را در CSS مشخص کنیم، موتور طرح مرورگر در نهایت آن مقدار را به پیکسل ( px ) تبدیل می‌کند. اینها «پیکسل‌های CSS» هستند که تاریخچه زیادی دارند و فقط با پیکسل‌هایی که روی صفحه‌نمایش دارید رابطه ضعیفی دارند.

برای مدت طولانی، تخمین تراکم پیکسلی صفحه نمایش هر کسی با 96DPI ("نقطه در هر اینچ") نسبتا معقول بود، به این معنی که هر مانیتوری تقریباً 38 پیکسل در سانتی متر خواهد داشت. با گذشت زمان، مانیتورها رشد کردند و/یا کوچک شدند یا پیکسل های بیشتری در همان سطح داشتند. این را با این واقعیت ترکیب کنید که بسیاری از محتواها در وب ابعاد خود را از جمله اندازه فونت‌ها را در px تعریف می‌کنند، و در نهایت با متن ناخوانا در این صفحه‌نمایش‌های با چگالی بالا ("HiDPI") مواجه می‌شویم. به عنوان یک اقدام متقابل، مرورگرها تراکم پیکسل واقعی مانیتور را مخفی می کنند و در عوض وانمود می کنند که کاربر صفحه نمایش 96 DPI دارد. واحد px در CSS نشان دهنده اندازه یک پیکسل در این نمایشگر مجازی 96 DPI است، از این رو "CSS Pixel" نامیده می شود. این واحد فقط برای اندازه گیری و موقعیت یابی استفاده می شود. قبل از اینکه رندر واقعی اتفاق بیفتد، تبدیل به پیکسل های فیزیکی اتفاق می افتد.

چگونه از این نمایش مجازی به نمایشگر واقعی کاربر برویم؟ devicePixelRatio را وارد کنید. این مقدار جهانی به شما می گوید که برای تشکیل یک پیکسل CSS به چند پیکسل فیزیکی نیاز دارید. اگر devicePixelRatio (dPR) 1 باشد، روی مانیتوری با تقریباً 96DPI کار می کنید. اگر صفحه نمایش شبکیه دارید، dPR شما احتمالاً 2 است. در تلفن‌ها، مواجهه با مقادیر dPR بالاتر (و عجیب‌تر) مانند 2 ، 3 یا حتی 2.65 غیر معمول نیست. توجه به این نکته ضروری است که این مقدار دقیق است، اما به شما اجازه نمی دهد مقدار DPI واقعی مانیتور را استخراج کنید. dPR 2 به این معنی است که 1 پیکسل CSS دقیقاً به 2 پیکسل فیزیکی نگاشت می شود.

مثال
مانیتور من طبق کروم dPR 1 دارد…

عرض آن 3440 پیکسل و عرض صفحه نمایش 79 سانتی متر است. که منجر به وضوح 110 DPI می شود. نزدیک به 96 اما نه کاملا. همچنین به همین دلیل است که <div style="width: 1cm; height: 1cm"> در اکثر نمایشگرها دقیقاً 1 سانتی متر نیست.

در نهایت، dPR همچنین می تواند تحت تأثیر ویژگی بزرگنمایی مرورگر شما قرار گیرد. اگر بزرگنمایی کنید، مرورگر dPR گزارش شده را افزایش می دهد و باعث می شود همه چیز بزرگتر شود. اگر در حین بزرگنمایی، devicePixelRatio در کنسول DevTools بررسی کنید، می توانید مقادیر کسری را مشاهده کنید.

DevTools به دلیل بزرگنمایی، انواع devicePixelRatio کسری PixelRatio را نشان می دهد.

بیایید عنصر <canvas> را به ترکیب اضافه کنیم. می‌توانید با استفاده از ویژگی‌های width و height ، مشخص کنید که می‌خواهید بوم چند پیکسل داشته باشد. بنابراین <canvas width=40 height=30> یک بوم با 40 در 30 پیکسل خواهد بود. البته این بدان معنا نیست که در ابعاد 40 در 30 پیکسل نمایش داده می شود. به‌طور پیش‌فرض، بوم از ویژگی width و height برای تعریف اندازه ذاتی خود استفاده می‌کند، اما می‌توانید به‌طور دلخواه با استفاده از تمام ویژگی‌های CSS که می‌شناسید و دوست دارید، اندازه بوم را تغییر دهید. با همه چیزهایی که تاکنون آموخته ایم، ممکن است به ذهنتان خطور کند که در هر سناریویی ایده آل نخواهد بود. یک پیکسل روی بوم ممکن است در نهایت چندین پیکسل فیزیکی یا فقط کسری از یک پیکسل فیزیکی را پوشش دهد. این می تواند به مصنوعات بصری ناخوشایند منجر شود.

به طور خلاصه: عناصر بوم دارای اندازه معینی برای تعیین ناحیه ای هستند که می توانید روی آن ترسیم کنید. تعداد پیکسل های بوم کاملاً مستقل از اندازه نمایش بوم است که در پیکسل های CSS مشخص شده است. تعداد پیکسل های CSS با تعداد پیکسل های فیزیکی یکسان نیست.

کمال پیکسل

در برخی از سناریوها، داشتن یک نقشه دقیق از پیکسل های بوم به پیکسل های فیزیکی مطلوب است. اگر این نقشه برداری به دست آید، آن را "pixel-perfect" می نامند. رندر کامل پیکسلی برای رندر خوانا متن بسیار مهم است، به خصوص در هنگام استفاده از رندر زیرپیکسلی یا هنگام نمایش گرافیک با خطوط روشنایی متناوب تراز شده محکم.

برای دستیابی به چیزی که تا حد امکان به یک بوم کامل پیکسلی در وب نزدیک شود، این رویکرد کمابیش مورد استفاده بوده است:

<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 یا سایر مقادیر غیرمستقیم مشخص کنید، ممکن است آنها به مقادیر پیکسل CSS کسری تبدیل شوند. یک عنصر با margin-left: 33% می تواند به یک مستطیل مانند این ختم شود:

DevTools که مقادیر پیکسل کسری را در نتیجه فراخوانی getBoundingClientRect() نشان می دهد.

پیکسل‌های CSS کاملا مجازی هستند، بنابراین داشتن کسری از پیکسل در تئوری مشکلی ندارد، اما مرورگر چگونه نگاشت پیکسل‌های فیزیکی را تشخیص می‌دهد؟ زیرا پیکسل های فیزیکی کسری یک چیز نیستند.

شکستن پیکسل

بخشی از فرآیند تبدیل واحد که از تراز کردن عناصر با پیکسل‌های فیزیکی مراقبت می‌کند، «قطع پیکسلی» نامیده می‌شود و همان کاری را که روی قلع می‌گوید انجام می‌دهد: مقادیر پیکسل کسری را به مقادیر صحیح پیکسل فیزیکی می‌چسباند. اینکه دقیقاً چگونه این اتفاق می افتد از مرورگر به مرورگر دیگر متفاوت است. اگر عنصری با عرض 791.984px در نمایشگری داشته باشیم که dPR برابر با 1 باشد، یک مرورگر ممکن است عنصر را با 792px پیکسل فیزیکی نمایش دهد، در حالی که مرورگر دیگری ممکن است آن را با 791px نمایش دهد. این فقط یک پیکسل خاموش است، اما یک پیکسل می تواند برای رندرهایی که باید از نظر پیکسل کامل باشند مضر باشد. این می تواند منجر به تاری یا حتی مصنوعات قابل مشاهده تر مانند اثر Moiré شود.

تصویر بالا شطرنجی از پیکسل های رنگی متفاوت است. تصویر پایین مانند تصویر بالا است، اما عرض و ارتفاع با استفاده از مقیاس دو خطی یک پیکسل کاهش یافته است. الگوی در حال ظهور اثر Moiré نامیده می شود.
(شاید مجبور باشید این تصویر را در یک برگه جدید باز کنید تا بدون هیچ مقیاسی روی آن اعمال شود.)

devicePixelContentBox

devicePixelContentBox جعبه محتوای یک عنصر را در واحدهای پیکسل دستگاه (یعنی پیکسل فیزیکی) به شما می دهد. این بخشی از ResizeObserver است. در حالی که ResizeObserver اکنون از زمان Safari 13.1 در همه مرورگرهای اصلی پشتیبانی می‌شود ، ویژگی devicePixelContentBox در حال حاضر فقط در Chrome 84+ موجود است.

همانطور که در ResizeObserver ذکر شد: مانند document.onresize برای عناصر است ، تابع callback یک 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']});

ویژگی box در شی option برای observer.observe() به شما امکان می دهد اندازه هایی را که می خواهید مشاهده کنید، تعیین کنید. بنابراین در حالی که هر ResizeObserverEntry همیشه borderBoxSize ، contentBoxSize و devicePixelContentBoxSize را ارائه می دهد (به شرطی که مرورگر از آن پشتیبانی کند)، پاسخ تماس تنها در صورتی فراخوانی می شود که هر یک از معیارهای کادر مشاهده شده تغییر کند.

با این ویژگی جدید، ما حتی می‌توانیم اندازه و موقعیت بوم خود را متحرک کنیم (به طور مؤثر مقادیر پیکسل کسری را تضمین می‌کند)، و هیچ اثر Moiré را در رندر مشاهده نمی‌کنیم. اگر می‌خواهید اثر Moiré را در رویکرد با استفاده از getBoundingClientRect() ببینید، و چگونه ویژگی ResizeObserver جدید به شما اجازه می‌دهد از آن اجتناب کنید، به نسخه آزمایشی کروم 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
}

نتیجه

پیکسل‌ها موضوعی به‌طور شگفت‌آور پیچیده در وب هستند و تا به حال هیچ راهی برای دانستن تعداد دقیق پیکسل‌های فیزیکی که یک عنصر در صفحه کاربر اشغال می‌کند وجود نداشت. ویژگی جدید devicePixelContentBox در ResizeObserverEntry این اطلاعات را در اختیار شما قرار می دهد و به شما امکان می دهد با <canvas> رندرهای کامل پیکسلی انجام دهید. devicePixelContentBox در Chrome 84 و بالاتر پشتیبانی می‌شود.