دسترسی انتخابگرهای خود را با CSS @scope at-rule محدود کنید، دسترسی انتخابگرهای خود را با CSS @scope at-rule محدود کنید

یاد بگیرید که چگونه از @scope برای انتخاب عناصر فقط درون یک زیردرخت محدود از DOM خود استفاده کنید.

منتشر شده: ۴ اکتبر ۲۰۲۳

Browser Support

  • کروم: ۱۱۸.
  • لبه: ۱۱۸.
  • فایرفاکس: ۱۴۶.
  • سافاری: ۱۷.۴.

Source

هنگام نوشتن انتخابگرها، ممکن است خود را بین دو دنیا گیر افتاده بیابید. از یک طرف می‌خواهید در مورد عناصری که انتخاب می‌کنید کاملاً دقیق باشید. از طرف دیگر، می‌خواهید انتخابگرهای شما به راحتی قابل لغو باشند و به ساختار DOM وابسته نباشند.

برای مثال، وقتی می‌خواهید «تصویر قهرمان در قسمت محتوای کامپوننت کارت» را انتخاب کنید - که انتخاب عنصر نسبتاً خاصی است - به احتمال زیاد نمی‌خواهید انتخابگری مانند .card > .content > img.hero بنویسید.

  • این انتخابگر از درجه اختصاصیت بسیار بالایی (0,3,1) برخوردار است که باعث می‌شود با افزایش کد، بازنویسی آن دشوار باشد.
  • با تکیه بر ترکیب‌کننده فرزند مستقیم، این ترکیب به شدت به ساختار DOM متصل است. در صورت تغییر نشانه‌گذاری، باید CSS خود را نیز تغییر دهید.

اما، شما همچنین نمی‌خواهید فقط img به عنوان انتخابگر آن عنصر بنویسید، زیرا این کار تمام عناصر تصویر را در صفحه شما انتخاب می‌کند.

پیدا کردن تعادل مناسب در این مورد اغلب چالش برانگیز است. در طول سال‌ها، برخی از توسعه‌دهندگان راه‌حل‌ها و راهکارهایی را برای کمک به شما در موقعیت‌هایی مانند این ارائه داده‌اند. به عنوان مثال:

  • روش‌هایی مانند BEM حکم می‌کنند که به آن عنصر، کلاسی از card__img card__img--hero بدهید تا میزان اختصاصی بودن پایین بماند و در عین حال بتوانید در انتخاب خود دقیق باشید.
  • راه‌حل‌های مبتنی بر جاوااسکریپت مانند Scoped CSS یا Styled Components با اضافه کردن رشته‌های تصادفی تولید شده - مانند sc-596d7e0e-4 - به انتخابگرهای شما، تمام انتخابگرهای شما را بازنویسی می‌کنند تا از هدف قرار دادن عناصر در سمت دیگر صفحه شما جلوگیری کنند.
  • بعضی از کتابخانه‌ها حتی انتخابگرها را به طور کلی حذف می‌کنند و از شما می‌خواهند که تریگرهای استایل‌بندی را مستقیماً در خود نشانه‌گذاری قرار دهید.

اما اگر به هیچ‌کدام از این‌ها نیاز نداشتید چه؟ چه می‌شد اگر CSS راهی به شما می‌داد تا در مورد عناصری که انتخاب می‌کنید کاملاً دقیق باشید، بدون اینکه مجبور باشید انتخابگرهایی با دقت بالا بنویسید یا انتخابگرهایی که به DOM شما وابسته هستند؟ خب، اینجاست که @scope وارد عمل می‌شود و راهی را برای شما فراهم می‌کند تا عناصر را فقط در یک زیردرخت از DOM خود انتخاب کنید.

معرفی @scope

با استفاده از @scope می‌توانید دسترسی انتخابگرهای خود را محدود کنید. این کار را با تنظیم ریشه دامنه که مرز بالایی زیردرختی را که می‌خواهید هدف قرار دهید تعیین می‌کند، انجام می‌دهید. با یک مجموعه ریشه دامنه، قوانین سبک موجود - که قوانین سبک دامنه‌دار نامیده می‌شوند - فقط می‌توانند از آن زیردرخت محدود DOM انتخاب کنند.

برای مثال، برای اینکه فقط عناصر <img> در کامپوننت .card را هدف قرار دهید، باید .card به عنوان ریشه‌ی scoping در @scope at-rule‎ تنظیم کنید.

@scope (.card) {
    img {
        border-color: green;
    }
}

قانون سبک محدوده‌بندی‌شده‌ی img { … } می‌تواند به‌طور مؤثر فقط عناصر <img> را انتخاب کند که در محدوده‌ی عنصر .card منطبق باشند.

برای جلوگیری از انتخاب عناصر <img> درون ناحیه محتوای کارت ( .card__content ) می‌توانید انتخابگر img را خاص‌تر کنید. راه دیگر برای انجام این کار استفاده از این واقعیت است که @scope at-rule یک محدودیت محدوده‌بندی را نیز می‌پذیرد که مرز پایینی را تعیین می‌کند.

@scope (.card) to (.card__content) {
    img {
        border-color: green;
    }
}

این قانون سبک‌دهیِ محدوده‌بندی‌شده فقط عناصر <img> را که بین عناصر .card و .card__content در درخت اجداد قرار دارند، هدف قرار می‌دهد. این نوع محدوده‌بندی - با مرز بالا و پایین - اغلب به عنوان محدوده‌ی دوناتی شناخته می‌شود.

انتخابگر :scope

به طور پیش‌فرض، تمام قوانین سبک‌دهیِ محدوده‌بندی‌شده نسبت به ریشه‌ی محدوده‌بندی‌شده هستند. همچنین می‌توان خود عنصر ریشه‌ی محدوده‌بندی‌شده را هدف قرار داد. برای این کار، از انتخابگر :scope استفاده کنید.

@scope (.card) {
    :scope {
        /* Selects the matched .card itself */
    }
    img {
       /* Selects img elements that are a child of .card */
    }
}

انتخابگرهای درون قوانین سبک scoped به طور ضمنی :scope به عنوان پیشوند دریافت می‌کنند. اگر می‌خواهید، می‌توانید با اضافه کردن :scope به عنوان پیشوند، در این مورد صریح باشید. به طور جایگزین، می‌توانید انتخابگر & را از CSS Nesting به عنوان پیشوند اضافه کنید.

@scope (.card) {
    img {
       /* Selects img elements that are a child of .card */
    }
    :scope img {
        /* Also selects img elements that are a child of .card */
    }
    & img {
        /* Also selects img elements that are a child of .card */
    }
}

یک محدودیت دامنه می‌تواند از شبه کلاس :scope برای الزام یک رابطه خاص با ریشه دامنه استفاده کند:

/* .content is only a limit when it is a direct child of the :scope */
@scope (.media-object) to (:scope > .content) { ... }

یک محدودیت دامنه همچنین می‌تواند با استفاده از :scope به عناصر خارج از ریشه دامنه آنها اشاره کند. برای مثال:

/* .content is only a limit when the :scope is inside .sidebar */
@scope (.media-object) to (.sidebar :scope .content) { ... }

خودِ قوانین سبکِ scoped نمی‌توانند از زیردرخت فرار کنند. انتخاب‌هایی مانند :scope + p نامعتبر هستند زیرا سعی می‌کنند عناصری را انتخاب کنند که در محدوده نیستند.

@scope و ویژگی

انتخابگرهایی که در مقدمه برای @scope استفاده می‌کنید، بر میزان اختصاصی بودن انتخابگرهای موجود تأثیری ندارند. در مثال ما، میزان اختصاصی بودن انتخابگر img همچنان (0,0,1) است.

@scope (#sidebar) {
  img { /* Specificity = (0,0,1) */
    ...
  }
}

ویژگی :scope مانند یک شبه کلاس معمولی، یعنی (0,1,0) است.

@scope (#sidebar) {
  :scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */
    ...
  }
}

در مثال زیر، به صورت داخلی، & به انتخابگری که برای ریشه‌ی تعیین محدوده استفاده می‌شود، بازنویسی می‌شود و درون یک انتخابگر :is() قرار می‌گیرد. در نهایت، مرورگر از :is(#sidebar, .card) img به عنوان انتخابگر برای انجام تطبیق استفاده می‌کند. این فرآیند به عنوان desugaring شناخته می‌شود.

@scope (#sidebar, .card) {
    & img { /* desugars to `:is(#sidebar, .card) img` */
        ...
    }
}

از آنجا که & با استفاده از :is() ‎ از حالت اشباع خارج می‌شود، میزان اختصاصی بودن & طبق قوانین :is() ‎ محاسبه می‌شود: میزان اختصاصی بودن & میزان اختصاصی بودن خاص‌ترین آرگومان آن است.

در این مثال، میزان خصوصیت :is(#sidebar, .card) ‎ برابر با میزان خصوصیت خاص‌ترین آرگومان آن، یعنی #sidebar ‎، است و بنابراین می‌شود (1,0,0) . این را با میزان خصوصیت img - که برابر با (0,0,1) است - ترکیب کنید و در نهایت به (1,0,1) به عنوان میزان خصوصیت برای کل سلکتور پیچیده می‌رسید.

@scope (#sidebar, .card) {
  & img { /* Specificity = (1,0,0) + (0,0,1) = (1,0,1) */
    ...
  }
}

تفاوت بین :scope و & درون @scope

گذشته از تفاوت‌ها در نحوه محاسبه‌ی ویژگی، تفاوت دیگر بین :scope و & این است که :scope ریشه‌ی تطبیق‌یافته‌ی حوزه‌بندی را نشان می‌دهد، در حالی که & انتخابگری را نشان می‌دهد که برای تطبیق ریشه‌ی تطبیق استفاده می‌شود.

به همین دلیل، می‌توان از & چندین بار استفاده کرد. این برخلاف :scope است که فقط می‌توانید یک بار از آن استفاده کنید، زیرا نمی‌توانید یک ریشه scoping را درون یک ریشه scoping دیگر تطبیق دهید.

@scope (.card) {
  & & { /* Selects a `.card` in the matched root .card */
  }
  :scope :scope { /* ❌ Does not work */
    
  }
}

دامنه بدون مقدمه

هنگام نوشتن استایل‌های درون‌خطی با عنصر <style> ، می‌توانید بدون مشخص کردن هیچ ریشه‌ی محدوده‌بندی، قوانین استایل را به عنصر والدِ دربرگیرنده‌ی عنصر <style> محدود کنید. این کار را با حذف مقدمه‌ی @scope انجام می‌دهید.

<div class="card">
  <div class="card__header">
    <style>
      @scope {
        img {
          border-color: green;
        }
      }
    </style>
    <h1>Card Title</h1>
    <img src="…" height="32" class="hero">
  </div>
  <div class="card__content">
    <p><img src="…" height="32"></p>
  </div>
</div>

در مثال بالا، قوانین scoped فقط عناصر درون div با نام کلاس card__header را هدف قرار می‌دهند، زیرا آن div عنصر والد عنصر <style> است.

@scope در آبشار

در داخل Cascade CSS ، @scope همچنین یک معیار جدید اضافه می‌کند: تعیین نزدیکی محدوده . این مرحله بعد از ویژگی خاص اما قبل از ترتیب ظاهر شدن می‌آید.

تجسم آبشار CSS.

طبق مشخصات :

هنگام مقایسه اعلان‌هایی که در قوانین سبک با ریشه‌های دامنه‌بندی مختلف ظاهر می‌شوند، اعلانی که کمترین پرش نسلی یا عنصر خواهر و برادری را بین ریشه دامنه‌بندی و موضوع قانون سبک دامنه‌بندی شده داشته باشد، برنده می‌شود.

این مرحله جدید هنگام تو در تو کردن چندین نوع از یک کامپوننت مفید است. به این مثال توجه کنید، که هنوز @scope استفاده نمی‌کند:

<style>
    .light { background: #ccc; }
    .dark  { background: #333; }
    .light a { color: black; }
    .dark a { color: white; }
</style>
<div class="light">
    <p><a href="#">What color am I?</a></p>
    <div class="dark">
        <p><a href="#">What about me?</a></p>
        <div class="light">
            <p><a href="#">Am I the same as the first?</a></p>
        </div>
    </div>
</div>

هنگام مشاهده آن بخش کوچک از کد، لینک سوم به جای black ، white خواهد بود، حتی با اینکه فرزند یک div با کلاس .light است که به آن اعمال شده است. این به دلیل معیار ترتیب ظاهر است که cascade در اینجا برای تعیین برنده استفاده می‌کند. می‌بیند که .dark a آخرین بار اعلام شده است، بنابراین از قانون .light a برنده می‌شود.

با معیار نزدیکی محدوده‌بندی، این مشکل اکنون حل شده است:

@scope (.light) {
    :scope { background: #ccc; }
    a { color: black;}
}

@scope (.dark) {
    :scope { background: #333; }
    a { color: white; }
}

از آنجایی که هر دو انتخابگر scoped a دارای ویژگی یکسانی هستند، معیار نزدیکی به دامنه شروع به کار می‌کند. این معیار هر دو انتخابگر را بر اساس نزدیکی به ریشه دامنه آنها وزن می‌دهد. برای عنصر سوم a ، فقط یک جهش به ریشه دامنه .light و دو جهش به ریشه دامنه .dark وجود دارد. بنابراین، انتخابگر a در .light برنده خواهد شد.

ایزولاسیون انتخابگر، نه ایزولاسیون استایل

بدانید که @scope دسترسی انتخابگرها را محدود می‌کند. این ویژگی، ایزولاسیون استایل را ارائه نمی‌دهد. ویژگی‌هایی که به فرزندان ارث می‌رسند، همچنان فراتر از حد پایین @scope به ارث می‌رسند. یکی از این ویژگی‌ها، ویژگی color است. هنگام تعریف آن در داخل محدوده‌ی دونات، color همچنان به فرزندان داخل سوراخ دونات ارث می‌رسد.

@scope (.card) to (.card__content) {
  :scope {
    color: hotpink;
  }
}

در این مثال، عنصر .card__content ‎ و فرزندانش رنگ hotpink دارند زیرا مقدار را از .card ‎ به ارث برده‌اند.