جمع‌بندی و تمرین محاسبه Specificity در انواع Selector

در طول این فصل، با انواع مختلف Selector آشنا شدیم: Type، Class، ID، Universal، Attribute، Combinatorها، Pseudo-class، Pseudo-element و Selectorهای تابعی. حالا وقتشه یه جمع‌بندی کامل داشته باشیم و ببینیم Specificity هرکدوم از این‌ها دقیقاً چطور محاسبه می‌شه و چطور می‌شه Specificity Selectorهای ترکیبی و پیچیده رو به‌درستی حساب کرد.

مرور چهار سطح Specificity

همون‌طور که در فصل ۲ دیدیم، Specificity به‌صورت یک عدد چهاربخشی نمایش داده می‌شه: (Inline, ID, Class, Type). حالا بیایم ببینیم هرکدوم از Selectorهایی که در این فصل یاد گرفتیم، دقیقاً تو کدوم سطح قرار می‌گیرن.

سطح Selectorهای این فصل که در این سطح قرار می‌گیرند
ID (سطح ۲) ID Selector (مثل #header)
Class (سطح ۳) Class Selector، Attribute Selector، Pseudo-class (مثل .box، [type="text"]، :hover، :nth-child()، :checked)
Type (سطح ۴) Type Selector، Pseudo-element (مثل p، ::before، ::first-letter)
وزن صفر Universal Selector (*)، Combinatorها (>، +، ~، فاصله خالی)، :where()

نکته مهم: خود Combinatorها (فاصله، >، +، ~) هیچ وزنی به Specificity اضافه نمی‌کنن؛ اونا فقط رابطه بین Selectorها رو مشخص می‌کنن. وزن نهایی، فقط از جمع Selectorهای واقعی داخل عبارت به‌دست میاد.

مثال ۱: ترکیب Combinator با چند Selector

article > h2 + p.intro {
  font-size: 18px;
}

بیایم این Selector رو تیکه‌تیکه بشماریم:

  • article → Type Selector → یک واحد Type
  • > → Combinator → وزن ندارد
  • h2 → Type Selector → یک واحد Type
  • + → Combinator → وزن ندارد
  • p → Type Selector → یک واحد Type
  • .intro → Class Selector → یک واحد Class

مجموع Specificity: (0, 0, 1, 3) — یک Class و سه Type.

مثال ۲: ترکیب Pseudo-class و Pseudo-element

ul li:nth-child(2)::before {
  content: "→ ";
}
  • ul → Type → یک واحد Type
  • li → Type → یک واحد Type
  • :nth-child(2) → Pseudo-class → یک واحد Class
  • ::before → Pseudo-element → یک واحد Type

مجموع Specificity: (0, 0, 1, 3) — یک Class و سه Type.

مثال ۳: نقش :is() و :where() در محاسبه

section :is(.card, #featured) p {
  color: darkslategray;
}

در اینجا :is() وزن بالاترین آرگومان داخلش رو می‌گیره. بین .card (Class) و #featured (ID)، بالاترین وزن مربوط به ID است. پس:

  • section → یک واحد Type
  • :is(.card, #featured) → وزن ID را می‌گیرد → یک واحد ID
  • p → یک واحد Type

مجموع Specificity: (0, 1, 0, 2).

حالا اگه همین Selector رو با :where() بازنویسی کنیم:

section :where(.card, #featured) p {
  color: darkslategray;
}

در این حالت، :where() همیشه وزن صفر داره، صرف‌نظر از این‌که #featured داخلش باشه. پس Specificity کل فقط از section و p میاد: (0, 0, 0, 2).

مثال ۴: نقش :not() و :has() در محاسبه

li:not(.disabled) {
  cursor: pointer;
}

article:has(img.featured) {
  border: 2px solid gold;
}

برای li:not(.disabled): li یک واحد Type، و :not() وزن آرگومان داخلش (یعنی .disabled، یک Class) رو می‌گیره. مجموع: (0, 0, 1, 1).

برای article:has(img.featured): article یک واحد Type، و :has() وزن بالاترین آرگومان داخلش رو می‌گیره؛ داخلش img (Type) و .featured (Class) هست، پس وزن Class برنده می‌شه. مجموع: (0, 0, 1, 2).

مثال ۵: یک Selector واقعاً پیچیده

#sidebar .widget-list > li.active:hover a[href^="https"]::after {
  color: gold;
}

بیایم قدم‌به‌قدم بشماریم:

  • #sidebar → ID → یک واحد ID
  • .widget-list → Class → یک واحد Class
  • > → Combinator → وزن ندارد
  • li → Type → یک واحد Type
  • .active → Class → یک واحد Class
  • :hover → Pseudo-class → یک واحد Class
  • a → Type → یک واحد Type
  • [href^="https"] → Attribute → یک واحد Class
  • ::after → Pseudo-element → یک واحد Type

مجموع Specificity: (0, 1, 4, 3) — یک ID، چهار Class (شامل دو Class واقعی، یک Pseudo-class و یک Attribute)، و سه Type (شامل یک Pseudo-element).

یک اشتباه رایج در محاسبه

خیلی‌ها فکر می‌کنن طول یا پیچیدگی ظاهری یک Selector، معیار Specificity‌شه؛ در حالی که فقط نوع Selectorهای داخلش مهمه:

/* این Selector طولانی است، ولی Specificity پایینی دارد */
header nav ul li a {
  color: navy;
}

/* این Selector کوتاه است، ولی Specificity بالاتری دارد */
#nav-link {
  color: crimson;
}

Selector اول شامل پنج Type Selector است: (0, 0, 0, 5). Selector دوم شامل فقط یک ID است: (0, 1, 0, 0). با وجود این‌که Selector دوم خیلی کوتاه‌تره، چون سطح ID از سطح Type بالاتره، مقدار #nav-link برنده می‌شه؛ صرف‌نظر از تعداد Selectorهای Type در طرف مقابل.

روش عملی برای محاسبه سریع Specificity

برای محاسبه سریع Specificity یک Selector پیچیده، این مراحل رو دنبال کن:

  1. هر ID Selector رو بشمار و در ستون دوم (ID) قرار بده.
  2. هر Class، Attribute Selector و Pseudo-class رو بشمار و در ستون سوم (Class) قرار بده.
  3. هر Type Selector و Pseudo-element رو بشمار و در ستون چهارم (Type) قرار بده.
  4. Combinatorها، Universal Selector و :where() رو کاملاً نادیده بگیر (وزن صفر).
  5. برای :is()، :not() و :has()، فقط وزن بالاترین آرگومان داخلی رو حساب کن، نه مجموع همه آرگومان‌ها.

نکات مهم

  • مقایسه Specificity همیشه رقم‌به‌رقم و از چپ به راست انجام می‌شه، نه با جمع کردن به یک عدد واحد.
  • طول ظاهری یک Selector هیچ ارتباطی با میزان Specificity‌ش نداره.
  • :is()، :not() و :has() وزن بالاترین آرگومان داخلی رو می‌گیرن؛ :where() همیشه وزن صفر داره.

اشتباهات رایج

  • تصور اینکه Selectorهای طولانی‌تر همیشه Specificity بالاتری دارن.
  • فراموش کردن اینکه Combinatorها هیچ وزنی اضافه نمی‌کنن.
  • محاسبه اشتباه Specificity برای :is()، :not() و :has()؛ با جمع کردن همه آرگومان‌ها به‌جای گرفتن فقط بالاترین.

جمع‌بندی

محاسبه Specificity در Selectorهای پیچیده، صرفاً نیازمند شمارش دقیق هر نوع Selector (ID، Class و مشابه‌ها، Type و مشابه‌ها) و نادیده گرفتن Combinatorهاست. برای Selectorهای تابعی مثل :is()، :not() و :has()، فقط وزن بالاترین آرگومان داخلی حساب می‌شه، در حالی که :where() همیشه وزن صفر دارد. تسلط بر این محاسبه، کلید پیش‌بینی درست رفتار Cascade در پروژه‌های واقعیه.