الحدّ من مدى وصول أدوات الاختيار باستخدام CSS @scope at-rule

تعرَّف على كيفية استخدام @scope لتحديد العناصر فقط ضمن شجرة فرعية محدودة من عناصر DOM.

دعم المتصفح

  • 118
  • 118
  • x
  • x

التفاصيل الدقيقة في كتابة أدوات اختيار لغة CSS

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

على سبيل المثال، عندما تريد اختيار "صورة الجزء الرئيسي في منطقة المحتوى في مكوِّن البطاقة"، وهي عنصر محدَّد نوعًا ما، لا تريد على الأرجح كتابة أداة اختيار، مثل .card > .content > img.hero.

  • تتسم أداة الاختيار هذه بخصوصية عالية جدًا لـ (0,3,1)، ما يُصعِّب تجاوزها مع زيادة الرمز البرمجي.
  • ومن خلال الاعتماد على أداة الدمج الفرعية المباشرة، تقترن بشدة ببنية DOM. وإذا تغيّر الترميز، يجب تغيير CSS أيضًا.

ولكنك لا تريد أيضًا كتابة img فقط كأداة اختيار لهذا العنصر، لأنّ ذلك سيحدِّد جميع عناصر الصور في صفحتك.

وغالبًا ما يكون تحقيق التوازن الصحيح في هذه الحالة تحديًا كبيرًا. على مر السنين، توصل بعض المطورين إلى حلول وحلول بديلة لمساعدتك في مثل هذه المواقف. مثال:

  • تفرض منهجيات مثل BEM أن تمنح هذا العنصر فئة card__img card__img--hero حتى تكون درجة الدقة منخفضة مع السماح لك بأن تكون محددًا في ما تختاره.
  • تعمل الحلول المستندة إلى JavaScript، مثل نطاق CSS أو المكوّنات ذات الأنماط على إعادة كتابة جميع أدوات الاختيار عن طريق إضافة سلاسل يتم إنشاؤها عشوائيًا، مثل sc-596d7e0e-4، إلى أدوات الاختيار لمنعها من استهداف العناصر على الجانب الآخر من صفحتك.
  • تلغي بعض المكتبات أدوات الاختيار تمامًا وتطلب منك وضع مشغّلات النمط مباشرةً في الترميز نفسه.

لكن ماذا لو لم تكن بحاجة إلى أي منها؟ ماذا لو أعطتك لغة CSS طريقةً لتكون محدّدة جدًا بشأن العناصر التي تختارها، بدون مطالبتك بكتابة أدوات اختيار عالية الدقة أو عناصر وثيقة الصلة بنموذج العناصر في المستند (DOM)؟ حسنًا، هذا هو دور @scope، الذي يوفّر لك طريقة لاختيار العناصر فقط ضمن شجرة فرعية من نموذج العناصر في المستند (DOM).

نقدّم لك @scope

من خلال @scope، يمكنك الحدّ من مدى وصول أدوات الاختيار التي تستخدمها. يمكنك إجراء ذلك من خلال تعيين جذر تحديد النطاق الذي يحدد الحد الأعلى للشجرة الفرعية التي تريد استهدافها. عند استخدام مجموعة جذر لتحديد النطاق، لا يمكن لقواعد الأنماط المضمّنة، التي تُسمى قواعد النمط المُفصَّل، الاختيار فقط من تلك الشجرة الفرعية المحدودة من نموذج العناصر في المستند (DOM).

على سبيل المثال، لاستهداف عناصر <img> فقط في المكوِّن .card، يمكنك ضبط .card باعتباره جذر تحديد نطاق @scope في القاعدة.

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

يمكن لقاعدة نمط النطاق img { … } اختيار عناصر <img> في نطاق العنصر .card المطابق فقط.

لمنع اختيار عناصر <img> داخل منطقة محتوى البطاقة (.card__content)، يمكنك جعل أداة الاختيار img أكثر تحديدًا. تتوفّر طريقة أخرى لإجراء ذلك من خلال استخدام حقيقة أنّ @scope ضمن القاعدة تقبل أيضًا حد النطاق الذي يحدّد الحدّ الأدنى.

@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 */
    }
}

تتم إضافة :scope ضمنيًا أدوات الاختيار داخل قواعد نمط النطاق. يمكنك إن أردت توضيح ذلك بوضوح من خلال إضافة :scope بنفسك في البداية. ويمكنك بدلاً من ذلك إضافة أداة اختيار & من تداخل CSS.

@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) { ... }

تجدر الإشارة إلى أن قواعد النمط المُفصَّل نفسها لا يمكنها تخطي الشجرة الفرعية. التحديدات مثل :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 و& في @scope

بالإضافة إلى الاختلافات في كيفية حساب الدقة، هناك اختلاف آخر بين :scope و& يتمثل في أن :scope تمثل جذر تحديد النطاق المطابق، في حين تمثل & أداة الاختيار المستخدمة لمطابقة جذر تحديد النطاق.

ولهذا السبب، من الممكن استخدام & عدة مرات. وهذا على عكس :scope الذي لا يمكنك استخدامه إلا مرة واحدة، حيث لا يمكنك مطابقة جذر تحديد نطاق داخل جذر تحديد النطاق.

@scope (.card) {
  & & { /* Selects a `.card` in the matched root .card */
  }
  :root :root { /* ❌ 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>

في المثال أعلاه، لا تستهدِف القواعد المحدَّدة إلا العناصر داخل div التي تحمل اسم الفئة card__header، لأنّ div هو العنصر الرئيسي للعنصر <style>.

@scope في التسلسل

داخل تتالي 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>

وعند عرض هذا الجزء الصغير من الترميز، سيكون الرابط الثالث هو white بدلاً من black، مع أنّ الرابط هو عنصر ثانوي لـ 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)