آکادمی بنو/آموزش CSS

Inheritance و Specificity در CSS

بهنام نوبخت · ۱۴۰۵/۶/۲۸

یکی از اولین مشکلاتی که هنگام کار جدی با CSS با آن روبه‌رو می‌شویم این است که گاهی یک قانون CSS را می‌نویسیم، اما مرورگر آن‌طور که انتظار داریم عمل نمی‌کند. ممکن است رنگ یک متن تغییر نکند، اندازه فونت از قانون دیگری گرفته شود یا یک Style جدید روی عنصر اعمال نشود.

در بسیاری از این موارد مشکل از اشتباه تایپی نیست؛ بلکه چند قانون CSS همزمان یک عنصر را هدف قرار داده‌اند و مرورگر باید تصمیم بگیرد که کدام قانون را اعمال کند. برای درک این رفتار باید با سه مفهوم مهم آشنا شویم: Inheritance یا ارث‌بری، Cascade یا آبشاری بودن CSS و Specificity یا میزان اختصاصی بودن Selector.

این سه مفهوم پایه بسیاری از رفتارهای CSS هستند و درک درست آن‌ها باعث می‌شود به‌جای استفاده مکرر از !important یا Selectorهای بسیار پیچیده، بتوانیم دلیل رفتار CSS را پیدا و مشکل را اصولی برطرف کنیم.

Inheritance چیست؟

Inheritance به معنی ارث‌بری است.

در CSS بعضی ویژگی‌ها می‌توانند به صورت پیش‌فرض از عنصر والد به فرزند منتقل شوند.

برای مثال:

body {
    color: blue;
}

اگر داخل body چندین عنصر داشته باشیم:

<body>

    <h1>عنوان صفحه</h1>

    <p>این یک متن است.</p>

    <div>
        <span>متن داخل Span</span>
    </div>

</body>

ویژگی color معمولاً به فرزندان منتقل می‌شود؛ بنابراین اگر برای آن‌ها رنگ دیگری مشخص نکرده باشیم، متن آن‌ها نیز از رنگ والد استفاده می‌کند.

CSS مشخص می‌کند که هر Property به صورت پیش‌فرض inherited است یا خیر. ویژگی‌هایی مانند color نمونه‌ای از Propertyهای inherited هستند.

والد و فرزند در CSS

برای درک Inheritance ابتدا باید مفهوم والد و فرزند را بشناسیم.

در این HTML:

<div class="card">
    <h2>عنوان</h2>
    <p>متن کارت</p>
</div>

می‌توانیم بگوییم:

.card
├── h2
└── p

بنابراین:

  • div والد h2 است.

  • div والد p است.

  • h2 و p فرزندان div هستند.

اگر بنویسیم:

.card {
    color: blue;
}

ممکن است h2 و p نیز رنگ آبی را دریافت کنند، چون color از ویژگی‌های قابل ارث‌بری است.

همه ویژگی‌های CSS ارث‌بری نمی‌شوند

یک اشتباه رایج این است که تصور کنیم تمام Propertyهای CSS از والد به فرزند منتقل می‌شوند.

این‌طور نیست.

مثلاً:

.card {
    color: blue;
    width: 500px;
}

فرزند .card رنگ را می‌تواند به ارث ببرد، اما width: 500px به این معنی نیست که همه فرزندان نیز عرض ۵۰۰ پیکسل داشته باشند.

به همین دلیل Propertyها از نظر Inheritance با یکدیگر متفاوت هستند.

چند Property رایج که معمولاً ارث‌بری می‌شوند

از جمله ویژگی‌هایی که معمولاً با Inheritance سر و کار دارند:

color
font-family
font-size
font-weight
line-height
text-align

اما این موضوع را به‌عنوان یک قانون مطلق برای همه Propertyها در نظر نگیرید.

بهتر است هنگام کار با یک Property خاص، مستندات همان Property را بررسی کنید.

چند Property که معمولاً ارث‌بری نمی‌شوند

برای مثال:

width
height
margin
padding
border
background-color

اگر بنویسیم:

.parent {
    width: 500px;
}

این به معنی آن نیست که تمام فرزندان نیز width: 500px خواهند داشت.

تغییر مقدار به صورت مستقیم

Inheritance زمانی اتفاق می‌افتد که برای یک Property روی عنصر فرزند مقدار مناسب دیگری تعیین نشده باشد.

مثلاً:

body {
    color: blue;
}

p {
    color: red;
}

در اینجا:

<p>متن من</p>

رنگ p قرمز خواهد بود.

چرا؟

چون p مستقیماً هدف Selector دوم قرار گرفته است.

یعنی:

p {
    color: red;
}

بر مقدار inherited از body غلبه می‌کند.

یک قانون مهم:

Style مستقیمی که روی خود عنصر اعمال شده باشد، نسبت به مقداری که فقط از والد به ارث رسیده است، اولویت دارد.

کلمه کلیدی inherit

CSS یک Keyword به نام inherit دارد که می‌توانیم با استفاده از آن به صورت صریح بگوییم:

مقدار این Property را از والد بگیر.

مثلاً:

.parent {
    color: blue;
}

.child {
    color: inherit;
}

HTML:

<div class="parent">
    <p class="child">متن</p>
</div>

در این حالت p رنگ والد خود را می‌گیرد.

inherit را می‌توان برای Propertyهای مختلف استفاده کرد.

چه زمانی inherit کاربرد دارد؟

فرض کنید یک لینک داریم:

body {
    color: #222;
}

a {
    color: blue;
}

اگر بخواهیم لینک رنگ والد خود را بگیرد:

a {
    color: inherit;
}

این تکنیک در طراحی Componentها نیز بسیار کاربردی است.

Cascade چیست؟

حالا به بخش مهم‌تری می‌رسیم.

نام CSS از عبارت:

Cascading Style Sheets

گرفته شده است.

کلمه Cascading به این موضوع اشاره دارد که وقتی چند منبع یا چند قانون برای یک Property مقدار تعیین می‌کنند، مرورگر طبق الگوریتم Cascade تصمیم می‌گیرد کدام مقدار نهایی اعمال شود.

مثلاً:

p {
    color: blue;
}

p {
    color: red;
}

هر دو قانون p را هدف قرار داده‌اند.

پس مرورگر باید تصمیم بگیرد:

blue یا red?

در این مثال چون Selectorها Specificity یکسانی دارند و هر دو در شرایط یکسان قرار دارند، قانون دوم که دیرتر آمده است برنده می‌شود.

نتیجه:

color: red;

Source Order یا ترتیب قرارگیری قوانین

وقتی دو قانون از نظر شرایط Cascade و Specificity برابر باشند، ترتیب قرارگیری آن‌ها اهمیت پیدا می‌کند.

مثلاً:

p {
    color: blue;
}

p {
    color: red;
}

نتیجه:

red

اما اگر ترتیب را عوض کنیم:

p {
    color: red;
}

p {
    color: blue;
}

نتیجه:

blue

یعنی وقتی سایر شرایط برابر باشند، قانون بعدی برنده می‌شود.

Specificity چیست؟

Specificity را می‌توان به زبان ساده میزان اختصاصی بودن Selector در نظر گرفت.

فرض کنید:

p {
    color: blue;
}

.text {
    color: green;
}

و HTML:

<p class="text">سلام</p>

هر دو Selector این عنصر را انتخاب می‌کنند:

p

و:

.text

اما .text اختصاصی‌تر از p است.

بنابراین رنگ نهایی:

green

خواهد بود.

Specificity یکی از مراحل تصمیم‌گیری Cascade برای قوانین رقیب است.

سیستم امتیازدهی Specificity

برای یادگیری ساده Specificity، فعلاً Selectorها را در سه گروه اصلی بررسی می‌کنیم:

نوع Selector

وزن تقریبی

ID

1-0-0

Class، Attribute، Pseudo-Class

0-1-0

Type و Pseudo-Element

0-0-1

این اعداد را می‌توان مانند سه ستون در نظر گرفت:

ID - CLASS - TYPE

Type Selector

مثلاً:

p {
    color: blue;
}

دارای Specificity:

0-0-1

Class Selector

مثلاً:

.text {
    color: green;
}

دارای Specificity:

0-1-0

Class از Type Selector اختصاصی‌تر است.

ID Selector

مثلاً:

#main-title {
    color: red;
}

دارای Specificity:

1-0-0

ID از Class و Type وزن بیشتری دارد.

مقایسه ساده Specificity

فرض کنید:

p {
    color: blue;
}

.text {
    color: green;
}

#title {
    color: red;
}

و HTML:

<p id="title" class="text">
    سلام
</p>

هر سه Selector عنصر را هدف قرار می‌دهند.

اما:

p       → 0-0-1
.text   → 0-1-0
#title  → 1-0-0

بنابراین:

#title

برنده می‌شود.

نتیجه:

red

اگر چند Class داشته باشیم چه می‌شود؟

مثلاً:

.card {
    color: blue;
}

.card.featured {
    color: orange;
}

Selector دوم دو Class دارد:

0-2-0

در حالی که Selector اول:

0-1-0

است.

بنابراین Selector دوم Specificity بیشتری دارد.

Attribute Selector هم Specificity دارد

یادتان هست در مقاله قبل با Attribute Selector آشنا شدیم؟

مثلاً:

input[type="email"] {
    border-color: green;
}

Attribute Selector از نظر Specificity در گروه Class قرار می‌گیرد.

یعنی:

0-1-0

بنابراین:

input[type="email"] {
    color: green;
}

از:

input {
    color: red;
}

اختصاصی‌تر است.

Pseudo-Class هم در گروه Class قرار می‌گیرد

برای مثال:

button:hover {
    background-color: orange;
}

:hover یک Pseudo-Class است.

از نظر Specificity مانند یک Class در نظر گرفته می‌شود.

بنابراین در این مثال:

button

یک Type Selector دارد:

0-0-1

و:

:hover

یک Pseudo-Class:

0-1-0

در مجموع:

0-1-1

Pseudo-Element با Pseudo-Class متفاوت است

مثلاً:

p::first-letter {
    font-size: 2rem;
}

::first-letter یک Pseudo-Element است.

Pseudo-Element از نظر Specificity در گروه Type قرار می‌گیرد.

بنابراین:

p::first-letter

تقریباً:

0-0-2

خواهد داشت.

چند مثال برای محاسبه Specificity

مثال اول

p

نتیجه:

0-0-1

مثال دوم

.text

نتیجه:

0-1-0

مثال سوم

#title

نتیجه:

1-0-0

مثال چهارم

p.text

یک Type و یک Class:

0-1-1

مثال پنجم

#main .text

یک ID و یک Class:

1-1-0

مثال ششم

#main .text p

یک ID، یک Class و یک Type:

1-1-1

آیا عددها را باید مثل یک عدد معمولی جمع کنیم؟

نه.

بهتر است Specificity را به صورت سه ستون ببینیم:

ID - CLASS - TYPE

مثلاً:

1-0-0

از:

0-20-0

نیز در مقایسه ساده، در ستون ID برتری دارد.

یعنی نمی‌توانیم بگوییم:

0-20-0 > 1-0-0

فقط چون ۲۰ بزرگ‌تر از ۱ است.

ابتدا ستون ID مقایسه می‌شود، سپس Class و در نهایت Type.

یک مثال واقعی از Specificity

HTML:

<p id="description" class="important">
    این متن است.
</p>

CSS:

p {
    color: blue;
}

.important {
    color: green;
}

#description {
    color: red;
}

نتیجه:

red

چون:

p           → 0-0-1
.important  → 0-1-0
#description→ 1-0-0

ترکیب Selectorها Specificity را افزایش می‌دهد

فرض کنید:

p {
    color: blue;
}

.card p {
    color: green;
}

Selector اول:

0-0-1

Selector دوم:

0-1-1

پس:

.card p

اختصاصی‌تر است.

چرا نباید همیشه Specificity را افزایش دهیم؟

ممکن است برای حل یک مشکل CSS وسوسه شویم Selector را طولانی‌تر کنیم.

مثلاً ابتدا:

.button {
    color: red;
}

کار نمی‌کند.

بعد:

.container .button {
    color: red;
}

باز هم مشکل داریم.

بعد:

main.container .content .button {
    color: red;
}

و همین‌طور ادامه دهیم.

این روش در پروژه‌های بزرگ مشکل ایجاد می‌کند، چون بعداً برای Override کردن همین Style باید Selector حتی قوی‌تری بنویسیم.

بهتر است تا حد امکان Selectorها ساده، قابل فهم و قابل نگهداری باشند.

!important چیست؟

احتمالاً این عبارت را زیاد دیده‌اید:

color: red !important;

!important یک مقدار معمولی CSS نیست؛ بلکه به Declaration می‌گوید که در Cascade به عنوان Important در نظر گرفته شود.

مثلاً:

p {
    color: blue !important;
}

در بسیاری از تعارض‌های معمول، Declaration دارای !important نسبت به Declaration معمولی اولویت بالاتری پیدا می‌کند.

آیا !important همیشه برنده است؟

این جمله را دقیقاً به عنوان یک قانون عمومی حفظ نکنید:

"!important همیشه برنده است."

Cascade فقط Specificity نیست و ترتیب کامل شامل Origin، Importance، Layer، Specificity و سایر مراحل است.

برای سطح فعلی دوره کافی است بدانیم که:

p {
    color: blue;
}

.special {
    color: red !important;
}

در یک سناریوی معمول از Styleهای نویسنده، Declaration دارای !important می‌تواند بر Declaration معمولی غلبه کند.

چرا نباید زیاد از !important استفاده کنیم؟

فرض کنید:

.button {
    color: blue !important;
}

بعد بخواهیم آن را تغییر دهیم:

.button.special {
    color: red;
}

ممکن است انتظار داشته باشیم red اعمال شود، اما !important در قانون اول باعث ایجاد تعارض می‌شود.

حالا ممکن است مجبور شویم بنویسیم:

.button.special {
    color: red !important;
}

و بعد اگر یک جای دیگر نیز !important اضافه شود، مدیریت CSS سخت‌تر خواهد شد.

به همین دلیل استفاده از !important برای حل مشکلات معمول Specificity توصیه نمی‌شود. بهتر است ابتدا علت تعارض را پیدا کنیم.

Cascade، Inheritance و Specificity چگونه با هم کار می‌کنند؟

این سه مفهوم را جداگانه یاد گرفتیم، اما در پروژه واقعی با هم کار می‌کنند.

فرض کنید:

<div class="card">
    <p class="description">
        متن محصول
    </p>
</div>

CSS:

.card {
    color: blue;
}

p {
    color: green;
}

.description {
    color: red;
}

برای p چند اتفاق وجود دارد.

ابتدا:

.card {
    color: blue;
}

رنگ می‌تواند از والد به فرزند منتقل شود.

اما p مستقیماً توسط این Rule هدف قرار گرفته:

p {
    color: green;
}

و همچنین:

.description {
    color: red;
}

پس مقدار inherited از .card در رقابت با Styleهای مستقیم قرار نمی‌گیرد؛ Style مستقیم روی خود p اعمال می‌شود.

بین دو قانون مستقیم:

p           → 0-0-1
.description→ 0-1-0

کلاس Specificity بیشتری دارد.

پس نتیجه:

red

یک مثال کامل برای درک موضوع

HTML:

<div class="profile">
    <h2 id="name" class="title">
        بهنام
    </h2>

    <p>
        توسعه‌دهنده وب
    </p>
</div>

CSS:

.profile {
    color: blue;
}

h2 {
    color: green;
}

.title {
    color: orange;
}

#name {
    color: red;
}

حالا برای h2:

قانون اول

.profile {
    color: blue;
}

این مقدار از والد به h2 به ارث می‌رسد.

قانون دوم

h2 {
    color: green;
}

مستقیماً h2 را هدف قرار می‌دهد.

قانون سوم

.title {
    color: orange;
}

از h2 اختصاصی‌تر است.

قانون چهارم

#name {
    color: red;
}

از همه این Selectorها Specificity بیشتری دارد.

بنابراین:

رنگ نهایی = قرمز

Source Order در Specificity برابر

حالا فرض کنید:

.title {
    color: blue;
}

.description {
    color: red;
}

و HTML:

<p class="title description">
    متن
</p>

هر دو Selector:

0-1-0

هستند.

پس اگر سایر شرایط Cascade برابر باشند، قانونی که دیرتر آمده است برنده می‌شود:

.description {
    color: red;
}

بنابراین متن قرمز خواهد شد.

یک قانون مهم برای Debug کردن CSS

وقتی می‌بینید یک Style کار نمی‌کند، سریع !important اضافه نکنید.

ابتدا این موارد را بررسی کنید:

۱. آیا Selector واقعاً عنصر را انتخاب می‌کند؟

مثلاً:

.card-title {
    color: red;
}

بررسی کنید عنصر واقعاً این کلاس را دارد:

<h2 class="card-title">

۲. آیا Property دیگری روی همان عنصر وجود دارد؟

مثلاً:

p {
    color: blue;
}

.description {
    color: red;
}

۳. Specificity کدام Selector بیشتر است؟

مقایسه کنید:

p              → 0-0-1
.description   → 0-1-0

۴. آیا Specificity برابر است؟

اگر برابر باشد، ترتیب قوانین را بررسی کنید.

۵. آیا !important وجود دارد؟

ممکن است قانون دیگری با:

color: red !important;

وجود داشته باشد.

استفاده از Developer Tools

یکی از بهترین ابزارها برای فهم Cascade، Developer Tools مرورگر است.

در مرورگر می‌توانید روی یک عنصر کلیک راست کرده و گزینه‌ای مانند:

Inspect

را انتخاب کنید.

در بخش Styles معمولاً می‌توانید ببینید:

  • کدام Selector روی عنصر Match شده است.

  • کدام Property اعمال شده است.

  • کدام Property نادیده گرفته شده و خط خورده است.

  • قانون از کدام فایل CSS آمده است.

  • چه Selectorهایی با یکدیگر رقابت می‌کنند.

مرورگرها در Developer Tools قوانین اعمال‌شده و همچنین Declarationهایی را که به دلیل Cascade یا Specificity اعمال نشده‌اند نمایش می‌دهند.

یک مثال برای Debug

فرض کنید:

button {
    background-color: blue;
}

.btn-primary {
    background-color: green;
}

#submit {
    background-color: red;
}

HTML:

<button id="submit" class="btn-primary">
    ارسال
</button>

اگر Inspect کنیم، ممکن است چیزی شبیه این ببینیم:

#submit
background-color: red;

.btn-primary
background-color: green;  ← overridden

button
background-color: blue;   ← overridden

این یکی از بهترین روش‌ها برای یادگیری عملی Specificity است.

Cascade فقط Specificity نیست

در سطح مقدماتی ممکن است تصور کنیم ترتیب تصمیم‌گیری فقط این است:

!important
↓
ID
↓
Class
↓
Tag
↓
آخرین قانون

این مدل برای شروع ساده است، اما ساختار واقعی CSS دقیق‌تر است.

Cascade ابتدا مواردی مانند Relevance و Origin و Importance را بررسی می‌کند؛ سپس در شرایط مناسب Specificity، Scoping Proximity و Source Order وارد تصمیم‌گیری می‌شوند. همچنین Cascade Layers می‌توانند روی اولویت Styleها تأثیر بگذارند.

در این دوره فعلاً لازم نیست تمام جزئیات Cascade Layers را حفظ کنید. هدف این مقاله این است که پایه ذهنی درست برای درک تعارض‌های CSS داشته باشید.

Cascade Layer چیست؟

در CSS مدرن مفهومی به نام Cascade Layer وجود دارد که با @layer تعریف می‌شود.

مثلاً:

@layer base {
    p {
        color: blue;
    }
}

@layer components {
    p {
        color: red;
    }
}

Layerها به توسعه‌دهنده اجازه می‌دهند اولویت گروه‌های مختلف Style را بهتر مدیریت کند و در پروژه‌های بزرگ وابستگی به Specificity را کاهش دهد.

اما چون Cascade Layers موضوع نسبتاً پیشرفته‌ای است، در این مرحله فقط با مفهوم آن آشنا می‌شویم و جزئیات آن را فعلاً وارد پروژه‌های اصلی نمی‌کنیم.

یک جدول سریع برای Specificity

Selector

Specificity

p

0-0-1

.text

0-1-0

#title

1-0-0

p.text

0-1-1

.card .title

0-2-0

#main .title

1-1-0

#main p

1-0-1

#main .card p

1-1-1

تمرین‌های مقاله

تمرین ۱ — Inheritance

HTML زیر را ایجاد کنید:

<div class="box">
    <h2>عنوان</h2>
    <p>متن</p>
</div>

و CSS زیر را بنویسید:

.box {
    color: blue;
}

بررسی کنید رنگ h2 و p چه می‌شود.

سپس فقط p را قرمز کنید:

p {
    color: red;
}

تمرین ۲ — Specificity

کد زیر را بررسی کنید:

p {
    color: blue;
}

.text {
    color: green;
}

#special {
    color: red;
}

HTML:

<p id="special" class="text">
    تست Specificity
</p>

پیش‌بینی کنید متن چه رنگی خواهد داشت.

سپس پاسخ خود را با مرورگر بررسی کنید.

تمرین ۳ — Specificity را محاسبه کنید

Specificity موارد زیر را محاسبه کنید:

p
.card
#header
.card p
#header .title
main#page .card p

تمرین ۴ — Source Order

کد زیر را بررسی کنید:

.title {
    color: blue;
}

.title {
    color: red;
}

رنگ نهایی را مشخص کنید.

سپس ترتیب را عوض کنید و نتیجه را دوباره بررسی کنید.

تمرین ۵ — پیدا کردن قانون برنده

کد زیر را اجرا کنید:

p {
    color: blue;
}

.article p {
    color: green;
}

.article .text {
    color: orange;
}

#special {
    color: red;
}

HTML:

<article class="article">
    <p id="special" class="text">
        متن آزمایشی
    </p>
</article>

برای هر Selector، Specificity را محاسبه کنید و مشخص کنید کدام قانون برنده می‌شود.

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

استفاده بی‌دلیل از !important

بد:

.title {
    color: red !important;
}

فقط چون قانون دیگری برنده شده است.

بهتر است ابتدا علت تعارض را پیدا کنید.

استفاده بیش از حد از ID

مثلاً:

#page #content #main .card .title {
    color: red;
}

این نوع Selectorها می‌توانند Specificity را بیش از حد بالا ببرند و Override کردن Style را دشوار کنند.

تصور اینکه آخرین قانون همیشه برنده است

این تصور اشتباه است.

ترتیب قرارگیری فقط یکی از عوامل Cascade است و Specificity، Importance، Origin، Layer و سایر مراحل نیز می‌توانند نتیجه را تغییر دهند.

تصور اینکه همه Propertyها ارث‌بری می‌شوند

این نیز اشتباه است.

مثلاً color معمولاً inherited است، اما width به صورت خودکار از والد به فرزند منتقل نمی‌شود.

اضافه کردن Selectorهای بیشتر برای حل هر مشکل

اگر یک قانون کار نمی‌کند، همیشه راه‌حل این نیست:

main .container .section .card .title {
    ...
}

ابتدا بررسی کنید مشکل از کجاست.

جمع‌بندی

در این مقاله با سه مفهوم بسیار مهم CSS آشنا شدیم.

Inheritance توضیح می‌دهد که چگونه بعضی Propertyها می‌توانند مقدار خود را از والد به فرزند منتقل کنند.

Cascade مشخص می‌کند وقتی چند Declaration مختلف برای یک Property وجود دارد، مرورگر چگونه بین آن‌ها تصمیم‌گیری می‌کند.

Specificity میزان اختصاصی بودن Selector را مشخص می‌کند و در شرایطی که چند قانون در یک Cascade origin و layer با هم رقابت می‌کنند، به مرورگر کمک می‌کند قانون مناسب را انتخاب کند.

همچنین یاد گرفتیم که !important ابزار قدرتمندی است، اما نباید برای حل هر مشکل CSS از آن استفاده کنیم. اگر ساختار Selectorها، Cascade و Specificity را به‌درستی درک کنیم، بسیاری از مشکلات CSS بدون نیاز به !important قابل حل هستند.

از اینجا به بعد، می‌توانیم Selectorها را بسیار حرفه‌ای‌تر مدیریت کنیم. در مقاله بعدی یک قدم دیگر به سمت CSS حرفه‌ای برمی‌داریم و با متغیرهای CSS و توابع CSS آشنا می‌شویم.

۱۰

نظرات

کد در دسترس نیست

هنوز نظری ثبت نشده است.