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

كم عدد وحدات البكسل بالفعل في لوحة العرض؟

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

Browser Support

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

Source

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

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

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

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

مثال
تبلغ نسبة dPR لشاشتي 1 وفقًا لمتصفّح Chrome…

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

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

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

لنضِف العنصر <canvas> إلى المزيج. يمكنك تحديد عدد وحدات البكسل التي تريد أن تحتوي عليها لوحة العرض باستخدام السمتَين width وheight. وبالتالي، سيكون <canvas width=40 height=30> لوحة عرض بحجم 40 × 30 بكسل. ومع ذلك، لا يعني هذا أنّه سيتم عرضه بدقة 40 × 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>

قد يتساءل القارئ الذكي عمّا يحدث عندما لا تكون نسبة وحدات البكسل إلى وحدات البكسل على الجهاز قيمة عددية صحيحة. هذا سؤال جيد، وهو يسلّط الضوء على جوهر هذه المشكلة. بالإضافة إلى ذلك، إذا حدّدت موضع عنصر أو حجمه باستخدام نسب مئوية أو vh أو قيم غير مباشرة أخرى، من المحتمل أن يتم تحويلها إلى قيم بكسل CSS كسرية. يمكن أن ينتهي الأمر بعنصر يتضمّن margin-left: 33% بمستطيل على النحو التالي:

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

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

محاذاة البكسل

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

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

devicePixelContentBox

تعرض devicePixelContentBox مربّع محتوى العنصر بوحدات بكسل الجهاز (أي وحدات البكسل المادية). وهي جزء من ResizeObserver. على الرغم من أنّ ResizeObserver يتوفّر الآن في جميع المتصفّحات الرئيسية منذ الإصدار 13.1 من Safari، إلا أنّ السمة 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 (إذا كان المتصفّح يتيح ذلك)، لن يتم استدعاء دالة الرجوع إلا إذا تغيّر أي من مقاييس المربّع التي تم رصدها.

باستخدام هذه السمة الجديدة، يمكننا حتى تحريك حجم اللوحة وموضعها (ما يضمن فعليًا قيم وحدات البكسل الجزئية)، وعدم رؤية أي تأثيرات تموّجية على العرض. إذا أردت الاطّلاع على تأثير التموّج عند استخدام طريقة getBoundingClientRect()، وكيف تتيح لك السمة الجديدة ResizeObserver تجنُّب هذا التأثير، يمكنك الاطّلاع على العرض التوضيحي في الإصدار 84 من Chrome أو الإصدارات الأحدث.

رصد الميزات

للتحقّق مما إذا كان متصفّح المستخدم يتوافق مع 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 والإصدارات الأحدث.