داخل دالة polyfill لاستعلام الحاوية

Gerald Monaco
Gerald Monaco

طلبات بحث الحاوية هي ميزة جديدة في CSS تتيح لك كتابة منطق نمط يستهدف ميزات عنصر رئيسي (مثل عرضه أو ارتفاعه) لتحديد أنماط عناصره الثانوية. تم مؤخرًا إصدار تحديث كبير لميزة polyfill بالتزامن مع الانتقال إلى صفحة الدعم في المتصفحات.

وفي هذه المشاركة، ستتمكّن من إلقاء نظرة سريعة على كيفية عمل polyfill والتحديات التي يتغلب عليها وأفضل الممارسات عند استخدامه لتقديم تجربة مستخدم رائعة للمستخدمين.

الخيارات المتقدمة

الترجمة

عندما يصادف محلّل CSS اللغوي داخل متصفّح قاعدة غير معروفة، مثل قاعدة @container الجديدة، سيتجاهلها ببساطة كما لو لم يكن موجودًا مطلقًا. لذلك، إنّ أول وأهم شيء يجب أن تقوم به رموز polyfill هي تحويل طلب البحث @container إلى عنصر لا يمكن تجاهله.

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

قبل
@container (width > 300px) {
  /* content */
}
بعد
@media all {
  /* content */
}

قبل طلبات البحث عن الحاوية، لم تكن CSS توفّر طريقة تتيح للمؤلف تفعيل مجموعات من القواعد أو إيقافها عشوائيًا. لتعويض هذا السلوك، يجب تحويل القواعد داخل استعلام الحاوية أيضًا. يتم تخصيص معرّف فريد لكل @container (على سبيل المثال، 123)، والذي يُستخدم لتحويل كل أداة اختيار بحيث يتم تطبيقها فقط عندما يحتوي العنصر على سمة cq-XYZ بما في ذلك هذا المعرّف. سيتم تعيين هذه السمة بواسطة رمز polyfill في وقت التشغيل.

قبل
@container (width > 300px) {
  .card {
    /* ... */
  }
}
بعد
@media all {
  .card:where([cq-XYZ~="123"]) {
    /* ... */
  }
}

لاحِظ استخدام الفئة الزائفة :where(...). عادةً ما يؤدي تضمين محدِّد سمة إضافي إلى زيادة خصوصية هذه المحدِّد. باستخدام الفئة الزائفة، يمكن تطبيق الشرط الإضافي مع الحفاظ على الخصوصية الأصلية. لمعرفة سبب أهمية ذلك، ضع في اعتبارك المثال التالي:

@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}

واعتمادًا على CSS هذه، يجب أن يتضمّن العنصر الذي يتضمّن الفئة .card دائمًا color: red، لأنّ القاعدة اللاحقة ستلغي دائمًا القاعدة السابقة باستخدام أداة الاختيار والدقة نفسَيهما. وبالتالي، ستؤدي ترجمة القاعدة الأولى وتضمين أداة اختيار سمة إضافية بدون :where(...) إلى زيادة الدقة، وإلى تطبيق color: blue بشكل خاطئ.

ومع ذلك، فإن الفئة الزائفة :where(...) جديدة نوعًا ما. بالنسبة إلى المتصفّحات التي لا تتوافق معه، يوفّر رمز polyfill حلاً آمنًا وسهلاً: يمكنك زيادة خصوصية القواعد عن قصد عبر إضافة أداة اختيار :not(.container-query-polyfill) وهمية إلى قواعد @container يدويًا:

قبل
@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}
بعد
@container (width > 300px) {
  .card:not(.container-query-polyfill) {
    color: blue;
  }
}

.card {
  color: red;
}

وهذا له عدد من الفوائد:

  • تم تغيير أداة الاختيار في CSS المصدر، لذا يظهر الفرق في الخصوصية بوضوح. يعمل هذا أيضًا كتوثيق حتى تعرف ما يتأثر عندما لم تعد بحاجة إلى دعم الحل البديل أو رمز polyfill.
  • ستكون خصوصية القواعد هي نفسها دائمًا، لأن polyfill لا تغيّرها.

أثناء التحويل البرمجي، سيستبدل رمز polyfill هذه المجموعة الوهمية بأداة اختيار السمة بنفس الدقة. لتجنّب حدوث أي مفاجآت، يستخدم رمز polyfill أداتَي الاختيار: يتم استخدام أداة اختيار المصدر الأصلية لتحديد ما إذا كان العنصر يجب أن يتلقّى سمة polyfill، بينما يتم استخدام أداة الاختيار التي تم تحويلها إلى نص.

العناصر الزائفة

قد تطرح على نفسك السؤال التالي: إذا ضبط رمز polyfill بعض سمات cq-XYZ على عنصر ما ليتضمن معرّف الحاوية الفريد 123، كيف يمكن إتاحة العناصر الصورية التي لا يمكن ضبط سمات عليها؟

ترتبط العناصر الزائفة دائمًا بعنصر حقيقي في نموذج العناصر في المستند (DOM) يُسمى العنصر المنشأ. أثناء الترجمة، يتم تطبيق المحدد الشرطي على هذا العنصر الحقيقي بدلاً من ذلك:

قبل
@container (width > 300px) {
  #foo::before {
    /* ... */
  }
}
بعد
@media all {
  #foo:where([cq-XYZ~="123"])::before {
    /* ... */
  }
}

وبدلاً من التحويل إلى #foo::before:where([cq-XYZ~="123"]) (والتي قد تكون غير صالحة)، يتم نقل أداة الاختيار الشرطي إلى نهاية العنصر الأصلي #foo.

ومع ذلك، هذا ليس كل ما هو مطلوب. لا يُسمح للحاوية بتعديل أي عنصر غير مضمَّن داخلها (ولا يمكن أن تكون الحاوية داخلها)، ولكن ضع في اعتبارك أن هذا ما سيحدث بالضبط إذا كان #foo هو عنصر الحاوية نفسه الذي يتم الاستعلام عنه. سيتم تغيير سمة #foo[cq-XYZ] عن طريق الخطأ، وسيتم تطبيق أي قواعد من قواعد #foo بشكل خاطئ.

لتصحيح ذلك، يستخدم رمز polyfill سمتين: واحدة يمكن تطبيقها على عنصر بواسطة الأصل فقط، والأخرى يمكن للعنصر تطبيقهما على نفسه. تُستخدَم السمة الأخيرة لأدوات الاختيار التي تستهدف العناصر الصورية.

قبل
@container (width > 300px) {
  #foo,
  #foo::before {
    /* ... */
  }
}
بعد
@media all {
  #foo:where([cq-XYZ-A~="123"]),
  #foo:where([cq-XYZ-B~="123"])::before {
    /* ... */
  }
}

بما أنّ الحاوية لن تطبّق السمة الأولى (cq-XYZ-A) على نفسها مطلقًا، لن تتطابق أداة الاختيار الأولى إلا إذا استوفت حاوية رئيسية مختلفة شروط الحاوية وطبّقتها.

الوحدات النسبية للحاويات

تتضمّن طلبات بحث الحاويات أيضًا بضع وحدات جديدة يمكنك استخدامها في CSS، مثل cqw وcqh لنسبة 1% من عرض وارتفاع أقرب حاوية رئيسية مناسبة (على التوالي). ولدعم ذلك، يتم تحويل الوحدة إلى تعبير calc(...) باستخدام خصائص CSS المخصّصة. يعيِّن polyfill قيم هذه الخصائص عبر أنماط مضمّنة في عنصر الحاوية.

قبل
.card {
  width: 10cqw;
  height: 10cqh;
}
بعد
.card {
  width: calc(10 * --cq-XYZ-cqw);
  height: calc(10 * --cq-XYZ-cqh);
}

هناك أيضًا وحدات منطقية، مثل cqi وcqb للحجم المضمَّن وحجم الكتلة (على التوالي). وهي أكثر تعقيدًا بعض الشيء، لأنّ المحاور المضمَّنة والكتلة يتم تحديدها من خلال writing-mode في العنصر الذي يستخدم الوحدة، وليس من خلال العنصر الذي يتم الاستعلام عنه. ولدعم ذلك، يُطبِّق رمز polyfill نمطًا مضمّنًا على أي عنصر يختلف writing-mode عن العنصر الرئيسي.

/* Element with a horizontal writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqw);
--cq-XYZ-cqb: var(--cq-XYZ-cqh);

/* Element with a vertical writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqh);
--cq-XYZ-cqb: var(--cq-XYZ-cqw);

يمكن الآن تحويل الوحدات إلى خاصية CSS المخصصة المناسبة كما في السابق.

أماكن إقامة

تضيف طلبات بحث الحاوية أيضًا بعض سمات CSS الجديدة، مثل container-type وcontainer-name. ونظرًا لعدم إمكانية استخدام واجهات برمجة التطبيقات مثل getComputedStyle(...) مع خصائص غير معروفة أو غير صالحة، يتم تحويلها أيضًا إلى خصائص CSS المخصّصة بعد تحليلها. إذا تعذّر تحليل أحد المواقع (على سبيل المثال، لأنّه يحتوي على قيمة غير صالحة أو غير معروفة)، يتم ببساطة تركه وحده ليتعامل المتصفّح معه.

قبل
.card {
  container-name: card-container;
  container-type: inline-size;
}
بعد
.card {
  --cq-XYZ-container-name: card-container;
  --cq-XYZ-container-type: inline-size;
}

ويتم تغيير هذه السمات كلما تم اكتشافها، ما يسمح لترميز polyfill باللعب مع ميزات CSS الأخرى مثل @supports. وتشكّل هذه الوظيفة أساس أفضل الممارسات لاستخدام رمز polyfill كما هو موضّح أدناه.

قبل
@supports (container-type: inline-size) {
  /* ... */
}
بعد
@supports (--cq-XYZ-container-type: inline-size) {
  /* ... */
}

بشكل تلقائي، يتم اكتساب خصائص CSS المخصّصة، ما يعني على سبيل المثال أن أي عنصر ثانوي للسمة .card سيتخذ القيمة --cq-XYZ-container-name و--cq-XYZ-container-type. ليس هذا بالتأكيد سلوك المواقع الأصلية. لحل هذه المشكلة، سيُدرِج رمز polyfill القاعدة التالية قبل أي أنماط مستخدِم، ما يضمن تلقّي كل عنصر القيم الأولية، ما لم يتم تجاوزه عمدًا باستخدام قاعدة أخرى.

* {
  --cq-XYZ-container-name: none;
  --cq-XYZ-container-type: normal;
}

أفضل الممارسات

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

أثناء التحميل الأولي، هناك الكثير من الأمور التي يجب أن تحدث قبل أن يتمكن polyfill من تخطيط الصفحة:

  • يجب تحميل رموز polyfill وإعدادها.
  • يجب تحليل أوراق الأنماط وتحويلها إلى نص. ونظرًا لعدم وجود أي واجهات برمجة تطبيقات يمكنها الوصول إلى المصدر الأولي لورقة أنماط خارجية، قد يلزم إعادة الجلب بشكل غير متزامن، على الرغم من أنه من الأفضل إجراء ذلك من خلال ذاكرة التخزين المؤقت للمتصفح فقط.

إذا لم يعالج رمز polyfill هذه المشاكل بعناية، من المحتمل أن تتراجع مؤشرات أداء الويب الأساسية.

لتسهيل توفير تجربة ممتعة للمستخدمين، تم تصميم رمز polyfill لمنح الأولوية لكل من مهلة الاستجابة الأولى (FID) ومتغيّرات التصميم التراكمية (CLS) على حساب سرعة عرض أكبر محتوى مرئي (LCP). بتعبير آخر، لا يضمن رمز polyfill أنّه سيتم تقييم طلبات بحث الحاوية قبل سرعة عرض محتوى الصفحة. وهذا يعني أنّه من أجل تقديم أفضل تجربة للمستخدم، يجب التأكّد من إخفاء أي محتوى سيتأثر حجمه أو موضعه باستخدام طلبات بحث الحاوية إلى أن يتم تحميل polyfill وتحويلها إلى نص. يمكنك إكمال هذا الإجراء باستخدام قاعدة @supports:

@supports not (container-type: inline-size) {
  #content {
    visibility: hidden;
  }
}

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

يُوصى باستخدام هذا الأسلوب لعدة أسباب:

  • وتؤدي أداة تحميل CSS الخالصة إلى تقليل النفقات العامة للمستخدمين الذين يستخدمون متصفّحات أحدث، مع تقديم ملاحظات بسيطة لمستخدمي المتصفحات القديمة والشبكات الأبطأ.
  • من خلال الجمع بين الموضع المطلق لأداة التحميل وvisibility: hidden، يمكنك تجنُّب متغيّرات التصميم.
  • بعد تحميل رموز polyfill، سيتوقّف شرط @supports هذا، وسيتم إظهار المحتوى الخاص بك.
  • في المتصفحات التي تتيح عمل طلبات بحث الحاوية، لن يجتاز الشرط مطلقًا، وبالتالي سيتم عرض الصفحة على صفحة الرسم الأولى كما هو متوقّع.

الخلاصة

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

لا يسعنا الانتظار لرؤية وتجربة الأشياء المدهشة التي ستبنيها بها.

شكر وتقدير

صورة رئيسية من تصوير دان كريستيان بادوريرز على Unسبلاش