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

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

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

دعم المتصفح

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

المصدر

الخلفية: وحدات بكسل CSS ووحدات بكسل لوحة الرسم ووحدات بكسل فعلية

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

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

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

مثال
شاشة العرض الخاصة بي لها درجة دقة 1 وفقًا لمتصفّح Chrome…

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

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

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

لنضيف عنصر <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% بمستطيل مثل هذا:

تعرض "أدوات مطوّري البرامج" قيمًا كسرية للبكسل نتيجة طلب 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é على العرض. إذا كنت تريد الاطّلاع على تأثير 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 والإصدارات الأحدث.