تودرتوی CSS با CSSNestedDeclarations بهبود می یابد، تودرتوی CSS با CSSNestedDeclarations بهبود می یابد

تاریخ انتشار: 8 اکتبر 2024

برای رفع برخی ایرادات عجیب و غریب در تودرتوی CSS، گروه کاری CSS تصمیم گرفت تا رابط CSSNestedDeclarations را به CSS Nesting Specification اضافه کند. با این افزودن، اعلان‌هایی که پس از قوانین سبک ارائه می‌شوند، در میان برخی پیشرفت‌های دیگر، دیگر تغییر نمی‌کنند.

این تغییرات در کروم از نسخه 130 موجود است و برای آزمایش در Firefox Nightly 132 و Safari Technology Preview 204 آماده است.

پشتیبانی مرورگر

  • کروم: 130.
  • لبه: 130.
  • فایرفاکس: 132.
  • سافاری: پشتیبانی نمی شود.

مشکل در تودرتو CSS بدون CSSNestedDeclarations

یکی از مشکلات با تودرتو CSS این است که، در اصل، قطعه زیر آنطور که در ابتدا انتظار داشتید کار نمی کند:

.foo {
    width: fit-content;

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

با نگاه کردن به کد، فرض می کنید که عنصر <div class=foo> دارای background-color green است زیرا background-color: green; اعلامیه آخر است اما در کروم قبل از نسخه 130 اینطور نیست. در آن نسخه‌هایی که از CSSNestedDeclarations پشتیبانی نمی‌کنند، background-color عنصر red است.

پس از تجزیه قانون واقعی کروم قبل از 130 استفاده به شرح زیر است:

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

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

CSS پس از تجزیه دو تغییر را تجربه کرد:

  • background-color: green; برای پیوستن به دو اعلامیه دیگر به سمت بالا تغییر مکان داد.
  • CSSMediaRule تودرتو بازنویسی شد تا اعلانات خود را با استفاده از & انتخابگر در یک CSSStyleRule اضافی بپیچد.

یکی دیگر از تغییرات معمولی که در اینجا مشاهده می کنید، ویژگی های حذف تجزیه کننده است که از آن پشتیبانی نمی کند.

می‌توانید با خواندن مجدد cssText از CSSStyleRule «CSS پس از تجزیه» را برای خودتان بررسی کنید.

خودتان آن را در این زمین بازی تعاملی امتحان کنید:

چرا این 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 به جای green red است.

رویکرد دیگری که گروه کاری CSS به آن توجه کرد این بود که همه اعلان‌های تودرتو را در یک قانون @nest قرار دهید. به دلیل تجربه عقب‌رفته توسعه‌دهنده که این امر باعث می‌شود، این مورد رد شد.

معرفی رابط CSSNestedDeclarations

راه حلی که گروه کاری CSS بر روی آن حل و فصل کرد، معرفی قانون اعلامیه های تودرتو است.

این قانون اعلان‌های تودرتو در Chrome با Chrome 130 اجرا می‌شود.

پشتیبانی مرورگر

  • کروم: 130.
  • لبه: 130.
  • فایرفاکس: 132.
  • سافاری: پشتیبانی نمی شود.

معرفی قانون اعلان‌های تودرتو، تجزیه‌کننده CSS را تغییر می‌دهد تا به‌طور خودکار اعلان‌های تودرتوی متوالی را در یک نمونه CSSNestedDeclarations بپیچد. وقتی سریالی می شود، این نمونه CSSNestedDeclarations به ویژگی cssRules از CSSStyleRule ختم می شود.

CSSStyleRule زیر را دوباره به عنوان مثال در نظر بگیرید:

.foo {
  width: fit-content;

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

هنگامی که در کروم 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 قرار می‌گیرد.

از Chrome 130 به بعد، اعلان‌ها دیگر بالا نمی‌روند، و در نهایت با دو شیء تودرتوی 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 را شناسایی کنید، می‌توانید از قطعه جاوا اسکریپت زیر استفاده کنید:

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