أنترنت

يتم تحسين تداخل CSS باستخدام CSSNestedDeclarations | مقالات | web.dev


تم النشر: 8 أكتوبر 2024

لإصلاح بعض المراوغات الغريبة في تداخل CSS، قررت مجموعة عمل CSS إضافة CSSNestedDeclarations واجهة لمواصفات تداخل CSS. مع هذه الإضافة، لم تعد الإعلانات التي تأتي بعد قواعد النمط تتغير، من بين بعض التحسينات الأخرى.

تتوفر هذه التغييرات في Chrome بدءًا من الإصدار 130 وهي جاهزة للاختبار في Firefox Nightly 132 وSafari Technology Preview 204.

مشكلة تداخل CSS بدون CSSNestedDeclarations

إحدى المشكلات المتعلقة بتداخل CSS هي أن المقتطف التالي لا يعمل كما قد تتوقع في البداية:

.foo {
    width: fit-content;

    @media screen {
        background-color: red;
    }
    
    background-color: green;
}

بالنظر إلى الكود، ستفترض أن <div class=foo> العنصر لديه green background-color لأن background-color: green; الإعلان يأتي أخيرًا. ولكن هذا ليس هو الحال في Chrome قبل الإصدار 130. في تلك الإصدارات التي تفتقر إلى الدعم CSSNestedDeclarations، ال background-color العنصر هو red.

بعد تحليل القاعدة الفعلية لمتصفح Chrome قبل 130 استخدامًا تكون كما يلي:

.foo {
    width: fit-content;
    background-color: green;

    @media screen {
        & {
            background-color: red;
        }
    }
}

خضع CSS بعد التحليل لتغييرين:

  • ال background-color: green; تم نقلها للانضمام إلى الإعلانين الآخرين.
  • المتداخلة CSSMediaRule تمت إعادة كتابتها لتغليف إعلاناتها بشكل إضافي CSSStyleRule باستخدام & محدد.

التغيير النموذجي الآخر الذي تراه هنا هو أن المحلل يتجاهل الخصائص التي لا يدعمها.

يمكنك فحص “CSS بعد التحليل” بنفسك من خلال قراءة ملف cssText من CSSStyleRule.

جربها بنفسك في هذا الملعب التفاعلي:

لماذا تتم إعادة كتابة CSS؟

لفهم سبب حدوث إعادة الكتابة الداخلية هذه، عليك أن تفهم كيف حدث ذلك CSSStyleRule يتم تمثيله في نموذج كائن CSS (CSSOM).

في Chrome قبل الإصدار 130، يتم إجراء تسلسل لمقتطف CSS الذي تمت مشاركته سابقًا إلى ما يلي:

↳ CSSStyleRule
  .type = STYLE_RULE
  .selectorText = ".foo"
  .resolvedSelectorText = ".foo"
  .specificity = "(0,1,0)"
  .style (CSSStyleDeclaration, 2) =
    - width: fit-content
    - background-color: green
  .cssRules (CSSRuleList, 1) =
    ↳ CSSMediaRule
    .type = MEDIA_RULE
    .cssRules (CSSRuleList, 1) =
      ↳ CSSStyleRule
        .type = STYLE_RULE
        .selectorText = "&"
        .resolvedSelectorText = ":is(.foo)"
        .specificity = "(0,1,0)"
        .style (CSSStyleDeclaration, 1) =
          - background-color: red

من بين جميع الخصائص التي أ CSSStyleRule له، الأمران التاليان ذوا صلة في هذه الحالة:

  • ال style الممتلكات التي هي أ CSSStyleDeclaration المثال الذي يمثل الإعلانات.
  • ال cssRules الممتلكات التي هي أ CSSRuleList الذي يحمل كل المتداخلة CSSRule أشياء.

نظرًا لأن جميع الإعلانات من مقتطف CSS تنتهي في ملف style ملكية CSStyleRule، هناك فقدان للمعلومات. عند النظر إلى style الملكية ليس من الواضح أن background-color: green وأعلن بعد المتداخلة CSSMediaRule.

↳ CSSStyleRule
  .type = STYLE_RULE
  .selectorText = ".foo"
  .style (CSSStyleDeclaration, 2) =
    - width: fit-content
    - background-color: green
  .cssRules (CSSRuleList, 1) =
    ↳ …

وهذا يمثل مشكلة، لأنه لكي يعمل محرك CSS بشكل صحيح، يجب أن يكون قادرًا على التمييز بين الخصائص التي تظهر في بداية محتويات قاعدة النمط وتلك التي تظهر متخللة مع قواعد أخرى.

أما بالنسبة للتصريحات داخل CSSMediaRule فجأة يتم لفها في CSSStyleRule: وذلك لأن CSSMediaRule لم يتم تصميمه لاحتواء الإعلانات.

لأن CSSMediaRule يمكن أن تحتوي على قواعد متداخلة – يمكن الوصول إليها من خلالها cssRules الخاصية – يتم تغليف الإعلانات تلقائيًا في ملف CSSStyleRule.

↳ CSSMediaRule
  .type = MEDIA_RULE
  .cssRules (CSSRuleList, 1) =
    ↳ CSSStyleRule
      .type = STYLE_RULE
      .selectorText = "&"
      .resolvedSelectorText = ":is(.foo)"
      .specificity = "(0,1,0)"
      .style (CSSStyleDeclaration, 1) =
        - background-color: red

كيفية حل هذا؟

نظرت مجموعة عمل CSS في عدة خيارات لحل هذه المشكلة.

كان أحد الحلول المقترحة هو تغليف جميع الإعلانات العارية في ملف متداخل CSSStyleRule مع محدد التداخل (&). تم تجاهل هذه الفكرة لأسباب مختلفة، بما في ذلك الآثار الجانبية غير المرغوب فيها التالية: & إزالة السكر ل :is(…):

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

خذ المثال التالي:

#foo, .foo, .foo::before {
  width: fit-content;
  background-color: red;

  @media screen {
    background-color: green;
  }
}

بعد تحليل هذا المقتطف يصبح هذا في Chrome قبل 130:

#foo,
.foo,
.foo::before {
  width: fit-content;
  background-color: red;

  @media screen {
    & {
      background-color: green;
    }
  }
}

هذه مشكلة لأن المتداخلة CSSRule مع & محدد:

  • يتسطح وصولا الى :is(#foo, .foo)، رمي بعيدا .foo::before من قائمة المحدد على طول الطريق.
  • لديه خصوصية (1,0,0) مما يجعل من الصعب الكتابة فوقه لاحقًا.

يمكنك التحقق من ذلك عن طريق فحص ما يتم تسلسل القاعدة إليه:

↳ CSSStyleRule
  .type = STYLE_RULE
  .selectorText = "#foo, .foo, .foo::before"
  .resolvedSelectorText = "#foo, .foo, .foo::before"
  .specificity = (1,0,0),(0,1,0),(0,1,1)
  .style (CSSStyleDeclaration, 2) =
    - width: fit-content
    - background-color: red
  .cssRules (CSSRuleList, 1) =
    ↳ CSSMediaRule
      .type = MEDIA_RULE
      .cssRules (CSSRuleList, 1) =
        ↳ CSSStyleRule
          .type = STYLE_RULE
          .selectorText = "&"
          .resolvedSelectorText = ":is(#foo, .foo, .foo::before)"
          .specificity = (1,0,0)
          .style (CSSStyleDeclaration, 1) =
            - background-color: green

بصريا يعني أيضا أن background-color ل .foo::before يكون red بدلاً من green.

هناك طريقة أخرى نظرت فيها مجموعة عمل CSS وهي جعلك تقوم بتغليف جميع الإعلانات المتداخلة في ملف @nest قاعدة. تم رفض هذا بسبب تجربة المطورين المتراجعة التي قد يسببها ذلك.

تقديم CSSNestedDeclarations واجهة

الحل الذي استقر عليه فريق عمل CSS هو تقديم قاعدة الإعلانات المتداخلة.

يتم تنفيذ قاعدة الإعلانات المتداخلة هذه في Chrome بدءًا من Chrome 130.

يؤدي إدخال قاعدة الإعلانات المتداخلة إلى تغيير محلل CSS ليقوم تلقائيًا بتغليف الإعلانات المتتابعة المتداخلة مباشرةً في ملف CSSNestedDeclarations مثال. عند إجراء تسلسل، هذا CSSNestedDeclarations المثال ينتهي في cssRules ملكية CSSStyleRule.

أخذ ما يلي CSSStyleRule كمثال مرة أخرى:

.foo {
  width: fit-content;

  @media screen {
    background-color: red;
  }
    
  background-color: green;
}

عند إجراء تسلسل في Chrome 130 أو الأحدث، يبدو الأمر كما يلي:

↳ CSSStyleRule
  .type = STYLE_RULE
  .selectorText = ".foo"
  .resolvedSelectorText = ".foo"
  .specificity = (0,1,0)
  .style (CSSStyleDeclaration, 1) =
    - width: fit-content
  .cssRules (CSSRuleList, 2) =
    ↳ CSSMediaRule
      .type = MEDIA_RULE
      .cssRules (CSSRuleList, 1) =
        ↳ CSSNestedDeclarations
          .style (CSSStyleDeclaration, 1) =
            - background-color: red
    ↳ CSSNestedDeclarations
      .style (CSSStyleDeclaration, 1) =
        - background-color: green

لأن CSSNestedDeclarations القاعدة تنتهي في CSSRuleList، المحلل اللغوي قادر على الاحتفاظ بموضع الملف background-color: green تصريح: بعد ال background-color: red الإعلان (الذي يعد جزءًا من CSSMediaRule).

علاوة على ذلك، وجود CSSNestedDeclarations المثال لا يقدم أيًا من الآثار الجانبية السيئة التي تسببت فيها الحلول المحتملة الأخرى، التي تم تجاهلها الآن: تتطابق قاعدة الإعلانات المتداخلة مع نفس العناصر والعناصر الزائفة تمامًا مثل قاعدة النمط الأصلي، مع نفس سلوك الخصوصية.

والدليل على ذلك هو إعادة قراءة cssText التابع CSSStyleRule. بفضل قاعدة الإعلانات المتداخلة، فهي نفس طريقة إدخال CSS:

.foo {
  width: fit-content;

  @media screen {
    background-color: red;
  }
    
  background-color: green;
}

ماذا يعني هذا بالنسبة لك

هذا يعني أن تداخل CSS أصبح أفضل كثيرًا اعتبارًا من Chrome 130. ولكن هذا يعني أيضًا أنه قد يتعين عليك مراجعة بعض التعليمات البرمجية الخاصة بك إذا كنت تقوم بتشذير الإعلانات العارية مع القواعد المتداخلة.

خذ المثال التالي الذي يستخدم الرائع @starting-style

/* This does not work in Chrome 130 */
#mypopover:popover-open {
  @starting-style {
    opacity: 0;
    scale: 0.5;
  }

  opacity: 1;
  scale: 1;
}

قبل Chrome 130، سيتم رفع هذه الإعلانات. سينتهي بك الأمر مع opacity: 1; و scale: 1; تصريحات الخوض في CSSStyleRule.style، يليه أ CSSStartingStyleRule ( يمثل @starting-style القاعدة) في CSSStyleRule.cssRules.

بدءًا من الإصدار 130 من Chrome وما بعده، لم تعد يتم رفع الإعلانات، وينتهي الأمر بإعلانين متداخلين CSSRule كائنات في CSSStyleRule.cssRules. بالترتيب: واحد CSSStartingStyleRule ( يمثل @starting-style القاعدة) وواحدة CSSNestedDeclarations الذي يحتوي على opacity: 1; scale: 1; تصريحات.

وبسبب هذا السلوك المتغير، فإن @starting-style يتم استبدال الإعلانات بالإعلانات الموجودة في ملف CSSNestedDeclarations على سبيل المثال، وبالتالي إزالة الرسوم المتحركة الإدخال.

لإصلاح الكود، تأكد من أن @starting-style تأتي الكتلة بعد التصريحات العادية مثل ذلك:

/* This works in Chrome 130 */
#mypopover:popover-open {
  opacity: 1;
  scale: 1;

  @starting-style {
    opacity: 0;
    scale: 0.5;
  }
}

إذا احتفظت بإعلاناتك المتداخلة أعلى القواعد المتداخلة عند استخدام CSS، فإن تداخل التعليمات البرمجية الخاصة بك يعمل في الغالب جيد مع جميع إصدارات جميع المتصفحات التي تدعم تداخل CSS.

وأخيرا، إذا كنت تريد ميزة الكشف عن ما هو متاح CSSNestedDeclarations، يمكنك استخدام مقتطف JavaScript التالي:

if (!("CSSNestedDeclarations" in self && "style" in CSSNestedDeclarations.prototype)) {
  // CSSNestedDeclarations is not available
}

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

زر الذهاب إلى الأعلى