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

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

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

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

  • 84
  • 84
  • 93
  • x

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

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

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

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

مثال
تتضمّن شاشتي معدّل تسجيل ثابت يبلغ 1 وفقًا لـ Chrome...

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

وأخيرًا، قد يتأثر أداء 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 افتراضية بحت، وبالتالي، لا بأس من الناحية النظرية استخدام كسور من البكسل، ولكن كيف يكتشف المتصفح التعيين إلى وحدات البكسل الفعلية؟ لأنّ وحدات البكسل المادية الكسرية لا تمثّل شيئًا.

التقاط صور Pixel

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

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

devicePixelContentBox

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