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 - TYPEType Selector
مثلاً:
p {
color: blue;
}دارای Specificity:
0-0-1Class Selector
مثلاً:
.text {
color: green;
}دارای Specificity:
0-1-0Class از Type Selector اختصاصیتر است.
ID Selector
مثلاً:
#main-title {
color: red;
}دارای Specificity:
1-0-0ID از 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-1Pseudo-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-1Selector دوم:
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
تمرینهای مقاله
تمرین ۱ — 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 .titlemain#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 آشنا میشویم.
نظرات
هنوز نظری ثبت نشده است.