عرض مثالي لوحدات البكسل باستخدام devicePixelContentBox

كم عدد البكسلات حقًا في اللوحة؟

منذ الإصدار 84 من Chrome، يتيح ResizeObserver قياس مربّع جديد يُسمى devicePixelContentBox، والذي يقيس أبعاد العنصر بالبكسل المادي. يتيح ذلك عرض رسومات دقيقة بدقة بكسل، خاصةً في سياق الشاشات ذات الكثافة العالية.

توافق المتصفّح

  • Chrome: 84
  • الحافة: 84
  • Firefox: 93
  • Safari: غير متوافق

المصدر

على الرغم من أنّنا غالبًا ما نعمل باستخدام وحدات طول مجردة مثل em أو % أو vh، إلا أنّ الأمر كله يعتمد على البكسل. عندما نحدّد حجم عنصر أو موضعه في CSS، سيحوّل محرّك تنسيق المتصفّح هذه القيمة في النهاية إلى وحدات بكسل (px). وهذه هي "وحدات بكسل CSS"، التي لها سجلّ كبير ولا ترتبط إلا بعلاقة فضفاضة بوحدات البكسل التي تظهر على شاشتك.

لفترة طويلة، كان من المعقول إلى حدٍ ما تقدير كثافة بكسل الشاشة لأي مستخدم باستخدام 96 نقطة لكل بوصة (PPI)، ما يعني أنّ أي شاشة معيّنة ستحتوي على 38 بكسل تقريبًا لكل سنتيمتر. بمرور الوقت، زاد حجم الشاشات و/أو صغر أو بدأت تحتوي على عدد أكبر من البكسل في المساحة السطحية نفسها. إضافةً إلى ذلك، فإنّ الكثير من المحتوى على الويب يحدّد أبعاده، بما في ذلك أحجام الخطوط، بوحدة px، ما يؤدي إلى ظهور نص غير مقروء على هذه الشاشات ذات الكثافة العالية (HiDPI). وكإجراء مضاد، تخفي المتصفّحات كثافة البكسل الفعلية للشاشة وتتظاهر بدلاً من ذلك بأنّ المستخدم لديه شاشة بدقة 96 نقطة لكل بوصة. تمثّل وحدة px في CSS حجم بكسل واحد على هذه الشاشة الافتراضية التي تبلغ دقتها 96 نقطة لكل بوصة، ومن هنا جاء اسم "بكسل CSS". لا تُستخدَم هذه الوحدة إلا للقياس والوضع. قبل إجراء أيّ عملية عرض فعلية، تتم تحويل الصورة إلى وحدات بكسل مادية.

كيف يمكننا الانتقال من هذا العرض الافتراضي إلى شاشة المستخدم الفعلية؟ يجب ملء الحقل "devicePixelRatio". توضّح لك هذه القيمة الشاملة عدد البكسلات الحقيقية التي تحتاج إليها لإنشاء بكسل واحد في CSS. إذا كانت قيمة devicePixelRatio (dPR) هي 1، يعني ذلك أنّك تعمل على شاشة بكثافة 96 نقطة في البوصة تقريبًا. إذا كانت لديك شاشة بدقة عالية، من المحتمل أن يكون لديك كثافة بكسل 2. على الهواتف، من الشائع أن تظهر قيم dPR أعلى (وأغرب) مثل 2 أو 3 أو حتى 2.65. من المهمّ ملاحظة أنّ هذه القيمة دقيقة، ولكنّها لا تسمح لك باستخراج قيمة النقاط لكل بوصة الفعلية للشاشة. يعني "معامل الكثافة النوعية للبكسل" الذي يبلغ 2 أنّه سيتم ربط بكسل واحد من CSS ببكسلَين فعليَّين بالضبط.

يبلغ عرضها 3440 بكسل ويبلغ عرض منطقة العرض 79 سم. ويؤدي ذلك إلى الحصول على دقة 110 نقطة في البوصة. قريب من 96، ولكن ليس تمامًا. وهذا هو السبب أيضًا في أنّ رمز <div style="width: 1cm; height: 1cm"> لن يُظهر حجمه بدقة 1 سم على معظم الشاشات.

أخيرًا، يمكن أن تتأثر دقة الصورة المعروضة أيضًا بميزة التكبير/التصغير في المتصفّح. في حال التكبير، يزيد المتصفّح من قيمة dPR المُبلّغ عنها، ما يؤدي إلى عرض كل العناصر بحجم أكبر. في حال وضع علامة في المربّع devicePixelRatio في DevTools Console أثناء التكبير/التصغير، يمكنك رؤية القيم الكسورية تظهر.

تعرض "أدوات مطوّري البرامج" مجموعة متنوعة من devicePixelRatio الكسور بسبب التكبير/التصغير.

لنضيف عنصر <canvas> إلى المزيج. يمكنك تحديد عدد البكسلات التي تريد أن تتضمّنها اللوحة باستخدام السمتَين width وheight. وبالتالي، سيكون <canvas width=40 height=30> لوحة بحجم 40 × 30 بكسل. ومع ذلك، لا يعني ذلك أنّه سيتم عرضه بدقة 40 x ‏30 بكسل. ستستخدم اللوحة تلقائيًا السمتَين width وheight لتحديد حجمها الأساسي، ولكن يمكنك تغيير حجم اللوحة بشكل عشوائي باستخدام جميع خصائص CSS التي تعرفها وتفضّلها. استنادًا إلى كل ما تعلمناه حتى الآن، قد يتبادر إلى ذهنك أنّ هذا الإجراء قد لا يكون مثاليًا في كل السيناريوهات. قد ينتهي الأمر بتغطية بكسل واحد على اللوحة لعدة وحدات بكسل فعلية، أو جزء بسيط من وحدة بكسل فعلية. ويمكن أن يؤدي ذلك إلى ظهور عناصر مرئية غير مرغوب فيها.

باختصار: عناصر اللوحة لها حجم محدّد لتحديد المنطقة التي يمكنك الرسم عليها. إنّ عدد بكسل اللوحة مستقل تمامًا عن حجم عرض اللوحة، والذي يتم تحديده بالبكسل في CSS. لا يتطابق عدد وحدات البكسل في CSS مع عدد وحدات البكسل الفعلية.

دقة رائعة

في بعض السيناريوهات، من المستحسن الحصول على تعيين دقيق من وحدات بكسل اللوحة إلى وحدات البكسل الفعلية. إذا تمّ تحقيق هذا الربط، يُعرف باسم "دقة بكسل مثالية". إنّ المعالجة الدقيقة للبكسل أمر بالغ الأهمية لعرض النص بوضوح، خاصةً عند استخدام المعالجة الدقيقة للوحدات الفرعية للبكسل أو عند عرض الرسومات التي تتضمّن خطوطًا محاذية بإحكام ذات سطوع متغيّر.

لتحقيق ما يقرب من لوحة بدقة بكسل على الويب، كان هذا النهج هو المفضّل بشكل أو بآخر:

<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% بمستطيل على النحو التالي:

تعرض "أدوات مطوّري البرامج" قيمًا كسرية للبكسل نتيجة طلب getBoundingClientRect().

تكون وحدات البكسل في CSS افتراضية بالكامل، لذا من الناحية النظرية، لا بأس بوجود أجزاء من وحدات البكسل، ولكن كيف يحدّد المتصفّح عملية الربط بوحدات البكسل الفعلية؟ لأنّ وحدات البكسل الحقيقية الكسورية غير متوفّرة.

ربط البكسل

يُعرف الجزء من عملية تحويل الوحدات الذي يهتم بمحاذاة العناصر مع وحدات البكسل الفعلية باسم "تثبيت البكسل"، وهو ينفّذ ما يُفترض به: يُثبّت قيم وحدات البكسل الكسورية إلى قيم وحدات البكسل الصحيحة. تختلف طريقة حدوث ذلك باختلاف المتصفّح. إذا كان لدينا عنصر بعرض 791.984px على شاشة يكون فيها dPR‏ = 1، قد يعرض أحد المتصفّحات العنصر بحجم 792px بكسل فعلي، بينما قد يعرضه متصفّح آخر بحجم 791px. هذا خطأ ببكسل واحد فقط، ولكن يمكن أن يكون هذا الخطأ ضارًا بالصور التي يجب أن تكون دقيقة بدقة بكسل. ويمكن أن يؤدي ذلك إلى التمويه أو ظهور المزيد من العناصر المرئية، مثل تأثير موري.

الصورة العلوية هي صورة نقطية تتألف من وحدات بكسل بألوان مختلفة. الصورة في أسفل الصفحة هي نفسها الصورة أعلاه، ولكن تم تقليل العرض والارتفاع بمقدار بكسل واحد باستخدام التكبير/التصغير الخطي. يُعرف النمط الذي يظهر باسم تأثير "موري".
(قد تحتاج إلى فتح هذه الصورة في علامة تبويب جديدة لعرضها بدون تطبيق أيّ تكبير عليها.)

devicePixelContentBox

يوفّر لك devicePixelContentBox مربّع محتوى العنصر بوحدات وحدات البكسل على الجهاز (أي وحدات البكسل الفعلية). وهي جزء من ResizeObserver. على الرغم من أنّ ResizeObserver متاح الآن في جميع المتصفحات الرئيسية منذ Safari 13.1، لا تتوفّر السمة devicePixelContentBox إلا في الإصدار 84 من Chrome والإصدارات الأحدث في الوقت الحالي.

كما هو موضّح في مقالة 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']});

تسمح لك سمة box في عنصر الخيارات الخاص بـ observer.observe() بتحديد المقاسات التي تريد مراقبتها. وبالتالي، على الرغم من أنّ كل 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
}

الخاتمة

تُعدّ البكسلات موضوعًا معقّدًا بشكلٍ مفاجئ على الويب، ولم يكن بإمكانك حتى الآن معرفة العدد الدقيق للبكسلات الفعلية التي يشغلها العنصر على شاشة المستخدم. يمنحك موقع devicePixelContentBox الجديد على ResizeObserverEntry هذه المعلومة ويسمح لك بإنشاء صور بدقة بكسل باستخدام <canvas>. تتوفّر devicePixelContentBox في الإصدار 84 من Chrome والإصدارات الأحدث.