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

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

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

  • کروم: 118.
  • لبه: 118.
  • فایرفاکس: پشت پرچم
  • سافاری: 17.4.

منبع

هنر ظریف نوشتن انتخابگرهای CSS

هنگام نوشتن انتخابگرها ممکن است بین دو دنیا سرگردان شوید. از یک طرف شما می خواهید در مورد عناصری که انتخاب می کنید کاملاً مشخص باشید. از سوی دیگر، شما می خواهید انتخابگرهای شما به راحتی قابل بازگردانی باقی بمانند و محکم به ساختار 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 می توانید دسترسی انتخابگرهای خود را محدود کنید. شما این کار را با تنظیم ریشه محدوده که مرز بالایی زیردرختی را که می خواهید هدف قرار دهید تعیین می کند، انجام می دهید. با یک مجموعه ریشه محدوده، قوانین سبک موجود - به نام قوانین سبک scoped - فقط می توانند از آن زیر درخت محدود DOM انتخاب کنند.

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

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

قانون scoped style 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 prepended می شوند. اگر بخواهید، می‌توانید با پیش‌فرض کردن :scope خودتان در مورد آن صریح باشید. از طرف دیگر، می‌توانید از CSS Nesting ، & Selector را به حالت قبل اضافه کنید.

@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 به عنوان انتخابگر برای انجام تطبیق استفاده می کند. این فرآیند به نام شیرین سازی شناخته می شود.

@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 و & inside @scope

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

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

@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 در آبشار

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

تجسم CSS Cascade.

طبق مشخصات :

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

این مرحله جدید هنگام تودرتو کردن چندین گونه از یک جزء مفید است. این مثال را در نظر بگیرید، که هنوز از @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 روی آن اعمال شده است. این به دلیل معیار ترتیب ظاهر است که آبشار در اینجا برای تعیین برنده استفاده می کند. می بیند که .dark a آخرین بار اعلام شده است، بنابراین از قانون .light a برنده خواهد شد

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

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

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

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

نکته پایانی: جداسازی انتخابگر، نه جداسازی سبک

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

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

در مثال بالا، عنصر .card__content و فرزندان آن دارای رنگ hotpink هستند زیرا مقدار را از .card به ارث می برند.

(عکس روی جلد رستم بورخانوف در Unsplash )