CSS الحديثة استبدلت جافاسكريبت التخطيط لدينا: استعلامات الحاويات و:has() والشبكة الفرعية في الإنتاج
جدول المحتويات
عن الكاتب

Ahmed Mahmoud
كاتب ومطوّر
مهندس برمجيات شغوف بتطوير الويب وتصميم تجربة المستخدم.
مؤسّس ديفيا · eng-ahmed.com ↗أهم النقاط
استعلامات الحاويات تتيح للمكوّن أن يحدد حجمه بناءً على الحاوية المحيطة به بدلاً من نافذة العرض. نضع container-type: inline-size على غلاف خارجي، ثم نكتب قواعد @container (min-width: 30rem) للمكوّن بداخله.
المحدد :has() هو محدد العنصر الأب في CSS. القاعدة .card:has(> img) تطابق البطاقة التي تحتوي صورة، وتُعاد مطابقتها لحظياً مع تغيّر حالة الـ DOM والنماذج، وهو ما يغطي حالات مثل body:has(dialog[open]).
الشبكة الفرعية، المكتوبة grid-template-rows: subgrid، تجعل الشبكة المتداخلة تتبنّى خطوط مسارات الشبكة الأب، فتتحاذى العناوين والتذييلات عبر البطاقات المتجاورة دون ارتفاعات ثابتة.
طبقات التتالي، المعلنة بصيغة @layer reset, base, components, utilities، تحدد ترتيب التجاوز حسب الطبقة لا حسب خصوصية المحدد، فتتفوق أداة بصنف واحد في طبقة لاحقة على محدد مكوّن بثلاثة أصناف.
استعلامات الحاويات و:has() والشبكة الفرعية وطبقات التتالي مدعومة في Chrome وSafari وFirefox. أما تموضع المرساة في CSS ودالة calc-size() فما زالت حصرية على Chromium، لذلك نشحنها كتحسين تدريجي فقط.
متى تستخدم استعلامات الحاويات بدلاً من استعلامات الوسائط؟
استخدم استعلام الحاوية كلما كان المكوّن قابلاً للظهور بأكثر من عرض عمود، واترك استعلامات الوسائط للأمور على مستوى الصفحة. استعلام الحاوية قاعدة CSS تُكتب بـ @container وتُحسم مقابل حجم أقرب عنصر أب يعلن container-type، وليس مقابل نافذة العرض.
في أنظمة التصميم لدينا حذف هذا مكوّن غلاف كنا نشحنه في عدة مشاريع: غلاف يستخدم ResizeObserver لقياس عرضه ثم تبديل الصنف is-narrow. هذا الغلاف متأخر دائماً بإطار واحد لأنه يقرأ التخطيط بعد أن يرسم المتصفح، فيظهر المكوّن عريضاً ثم يقفز ضيقاً. أما استعلام الحاوية فيُحسم أثناء التخطيط نفسه، فلا يوجد إطار وسيط.
القيمة container-type: inline-size تجعل المحور الأفقي وحده قابلاً للاستعلام وتطبق احتواء الحجم على هذا المحور. النتيجة العملية أن هذه الحاوية لم يعد بإمكانها أن تأخذ حجمها من عرض محتواها، وهذا ما يكسر العناصر التي تتقلص لمحتواها عند أول تبنٍّ للميزة. ووحدات استعلام الحاوية تتبع المحور نفسه: 1cqi تساوي واحداً بالمئة من العرض الداخلي للحاوية، لذلك clamp(1rem, 4cqi, 1.5rem) تكبّر الخط حسب البطاقة لا حسب الشاشة.
المزلق الذي يستهلك أكبر وقت في التنقيح هو أن العنصر لا يستطيع الاستعلام عن نفسه. إذا وُضع container-type على .card فلن تطابق أي قاعدة @container تستهدف .card أبداً. يجب أن تكون الحاوية عنصراً أباً لكل ما يستهدفه الاستعلام.
ماذا يستبدل المحدد :has() فعلياً؟
المحدد :has() يستبدل كود جافاسكريبت الذي يضيف صنفاً إلى العنصر الأب بسبب شيء بداخله. وهو محدد علاقي: الصيغة A:has(B) تختار العنصر A عند وجود عنصر تابع أو ابن أو شقيق يطابق B.
ثلاث قواعد تغطي معظم استخدامنا. القاعدة .field:has(input:user-invalid) تنسّق غلاف الحقل بعد أن يُدخل المستخدم قيمة غير صالحة فعلاً. والقاعدة .card:has(> img) تغيّر التخطيط للبطاقات التي تحتوي وسائط حقيقية فقط. والقاعدة body:has(dialog[open]) تطبق overflow: hidden دون أي مستمع أحداث، وقد أزالت أداة قفل التمرير التي ظل فريقنا يصونها لسنوات.
تفصيلتان تستحقان الحفظ. المحدد :has() يأخذ خصوصية أكثر وسائطه خصوصية، فـ .card:has(#hero) يحمل وزن المعرّف ما لم نغلّف الوسيط بـ :where(). كما أن :has() لا يمكن تعشيشه داخل :has() آخر ولا أن يحتوي عناصر زائفة.
بخصوص الأداء، الادعاء المطلق بأن :has() بطيء صار قديماً، فمحركات المتصفحات تحسّنه. ممارستنا هي إبقاء الهدف ضيقاً، بتفضيل .card:has(> img) على *:has(img)، بدلاً من تجنّب المحدد.
متى تحتاج الشبكة الفرعية بدلاً من شبكة متداخلة؟
استخدم الشبكة الفرعية عندما يجب أن تتحاذى أبناء عناصر شبكة منفصلة فيما بينها. الشبكة المتداخلة العادية display: grid تنشئ مسارات مستقلة يحددها محتواها، فتنتج ثلاث بطاقات بعناوين مختلفة الطول ثلاثة تخطيطات داخلية مختلفة. أما grid-template-rows: subgrid فتجعل الابن يتبنّى خطوط صفوف الأب.
تعمل خاصيتان معاً. الإعلان grid-row: span 3 يجعل البطاقة تشغل ثلاثة صفوف من الشبكة الأب، وgrid-template-rows: subgrid يخبر البطاقة باستخدام خطوط هذه الصفوف لأبنائها. فيقع كل عنوان على الخط نفسه وكل تذييل على الخط نفسه، دون أي ارتفاعات ثابتة.
البدائل التي استخدمها فريقنا قبل الشبكة الفرعية كانت جميعها أسوأ: ارتفاعات مثبتة يدوياً، أو قص الأسطر لإجبار كل عنوان على طول واحد، أو تمريرة جافاسكريبت تقيس أطول بطاقة ثم تكتب ارتفاعاً بالبكسل على البقية. والشبكة الفرعية تُطبق لكل محور على حدة، فيمكن تفعيلها على الصفوف مع تعريف أعمدة خاصة بك.
كيف توقف طبقات التتالي و@scope حروب الخصوصية؟
طبقات التتالي تحوّل ترتيب التجاوز إلى إعلان صريح بدل أن يكون نتيجة عرضية لمحدداتك. القاعدة في طبقة لاحقة تتفوق على القاعدة في طبقة سابقة بغض النظر عن الخصوصية، فتتجاوز أداة بصنف واحد مثل .text-muted في طبقة utilities محدداً بثلاثة أصناف مثل .nav .nav__link.is-active في طبقة components.
أهم قاعدة على الإطلاق في طبقات التتالي هي أن CSS غير المُطبّق داخل طبقة يتفوق على كل الطبقات. ملف أنماط خارجي واحد يُحمّل خارج أي طبقة يتفوق على تتاليك المرتب بالكامل، ولهذا نستورد أنماط المزوّدين بصيغة @import url(vendor.css) layer(vendor). والمزلق الثاني أن !important يعكس ترتيب الطبقات، فيتفوق إعلان !important في طبقة مبكرة على إعلان عادي في طبقة لاحقة.
القاعدة @scope هي الميزة المكمّلة. كتابة @scope (.card) to (.card__content) تطبق القواعد بين جذر وحدّ سفلي فقط، وهو عزل حقيقي للمكوّنات دون أسماء أصناف BEM الطويلة. وقد وصلت متأخرة عن الميزات الأربع الأخرى وكان Firefox آخر محرك يشحنها، لذلك تحقق من حالة الدعم الحالية قبل الاعتماد عليها.
أي ميزة CSS تستبدل أي كود جافاسكريبت؟
ResizeObserver مع تبديل الأصناف لنقاط توقف المكوّن يستبدله container-type مع @container، وهو متاح في كل المحركات منذ Firefox 110 في فبراير 2023.
تبديل أصناف العنصر الأب بناءً على حالة الأبناء أو النماذج يستبدله :has()، وهو متاح في كل المحركات منذ Firefox 121 في ديسمبر 2023.
قياس أطول بطاقة وكتابة ارتفاعات بالبكسل يستبدله grid-template-rows: subgrid، وهو متاح في كل المحركات منذ Chrome 117 في سبتمبر 2023.
حيل الخصوصية وسلاسل !important وألاعيب ترتيب حقن الأنماط يستبدلها @layer، وهي متاحة في كل المحركات منذ Chrome 99 وSafari 15.4 في عام 2022.
أداة قفل التمرير التي تحفظ موضع التمرير وتستعيده تستبدلها body:has(dialog[open]) مع overflow: hidden.
ما الذي ما زال غير آمن للشحن في 2026؟
تموضع المرساة في CSS واستيفاء الأحجام بالكلمات المفتاحية ما زالا حصريين على Chromium، لذلك نشحنهما فقط حيث يكون فقدانهما تدهوراً سلساً. تموضع المرساة، أي anchor-name وposition-anchor وposition-area، يربط النافذة المنبثقة بمشغّلها دون مكتبة تموضع جافاسكريبت، وهو في Chrome منذ الإصدار 125 دون إصدارات مقابلة في Safari وFirefox.
الثنائي interpolate-size: allow-keywords وcalc-size() يحرّك أخيراً height: auto، ويقع في الخانة الحصرية على Chromium نفسها. وللوحات القابلة للتوسّع نستخدم انتقال كسر الشبكة بدلاً منه، أي تحريك grid-template-rows من 0fr إلى 1fr مع overflow: hidden على العنصر الداخلي، لأنه يعمل في كل المحركات اليوم.
القاعدة التي يتبعها فريقنا: إذا كانت الميزة تغيّر بنية المعلومات، أي قدرة المستخدم على قراءة المحتوى أو إتمام المهمة، فيجب أن تكون مدعومة في المحركات الثلاثة. أما إذا كانت تغيّر اللمسات النهائية فقط، فنضعها خلف @supports وندع المحركات الأقدم تحصل على النسخة البسيطة. هذا الخط هو ما يمنع نافذة منبثقة من الهبوط في الزاوية الخطأ على Safari.
الأسئلة الشائعة
س: هل تحل استعلامات الحاويات محل استعلامات الوسائط تماماً؟
ج: لا. استعلامات الحاويات تحدد حجم المكوّن مقابل حاويته، بينما تبقى استعلامات الوسائط مسؤولة عن أمور الصفحة والبيئة التي لا تعرفها الحاوية، مثل تخطيط الصفحة العام وprefers-reduced-motion وprefers-color-scheme وأنماط الطباعة.
س: هل المحدد :has() بطيء؟
ج: اعتبار :has() مكلفاً تلقائياً نصيحة قديمة، لأن المحركات الحديثة تحسّنه. أبقِ الهدف ضيقاً بتفضيل .card:has(> img) على *:has(img)، وحلّل إعادة حساب الأنماط في أدوات المطور إذا شعرت ببطء في صفحة بعينها.
س: لماذا لا يطابق استعلام الحاوية أي شيء؟
ج: غالباً لأن العنصر يحاول الاستعلام عن نفسه. قاعدة @container تُحسم مقابل عنصر أب يعلن container-type، لا مقابل العنصر الذي تنسّقه القاعدة، لذلك انقل container-type إلى غلاف خارجي.
س: ما الفرق بين الشبكة الفرعية والشبكة المتداخلة؟
ج: الشبكة المتداخلة display: grid تنشئ مسارات مستقلة يحددها محتواها، بينما grid-template-rows: subgrid تتبنّى خطوط مسارات الأب فتتحاذى العناصر المتجاورة فيما بينها.
س: هل تعمل طبقات التتالي مع Tailwind CSS أو CSS-in-JS؟
ج: نعم. Tailwind CSS الإصدار 4 ينظم مخرجاته في @layer theme, base, components, utilities. والقاعدة الواجب تذكرها أن CSS خارج الطبقات يتفوق على كل الطبقات، لذلك استورد أنماط الطرف الثالث داخل طبقة مسماة إذا أردت تجاوزها.
قراءات إضافية
هندسة الواجهات
الوضع الداكن دون وميض في Next.js 16: كيف نستخدم سكربت سمة حاجبًا وcolor-scheme وزر تبديل آمنًا مع الـ Hydration
وميض الوضع الداكن عند إعادة تحميل الصفحة مشكلة توقيت: الخادم لا يستطيع قراءة localStorage. نشرح سكربت السمة الحاجب الذي نستخدمه في Next.js 16، ولماذا لا نضع السمة في cookie حفاظًا على الرسم الثابت، وكيف نبني زر تبديل يمر بالـ hydration دون أي عدم تطابق.
هندسة الواجهات
صور Open Graph الديناميكية في Next.js 16: كيف نبني opengraph-image.tsx ونتحقق من إصدار Satori الذي تضمّنه next/og ونحافظ على صحة معاينات الروابط
واجهت بطاقات المشاركة المولّدة لدينا ثلاث مفاجآت: خط WOFF2 لم يستطع المحرّك قراءته، وملف README لـ Satori يصف محرّكًا أحدث من المضمّن في Next.js، وصفحة ظلّت تعرض صورة الـ layout. هذه ملاحظاتنا الميدانية حول opengraph-image.tsx في Next.js 16 وأولوية البيانات الوصفية وفحص og:image قبل مشاركة أي رابط.