تاروپود — خودآموز HTML و CSS

فصل ۳ از ۱۱

پیشرفت ترم
۰٪

ترم ۲ · CSS: کنترل ظاهر

cascade، specificity و inheritance

فصل ۳پیش‌نمایش رایگان
۱۱ دقیقه مطالعه فصل ۳

در این فصل چه می‌سازی#

این کوتاه‌ترین فصلِ ترم است، ولی مهم‌ترینش. تا اینجا هر قاعده که نوشتی، تنها بود و کارش را می‌کرد. در دنیای واقعی این‌طور نیست: چند قاعده به یک عنصر می‌خورند و رنگ‌های متفاوت می‌خواهند. کدام برنده می‌شود؟ بیشترِ کسانی که CSS را رها می‌کنند، دقیقاً همین‌جا گیر می‌کنند — چون حدس می‌زنند. آخر این فصل تو حدس نمی‌زنی؛ می‌شماری و از قبل می‌دانی کدام رنگ برنده است.

یک ترازو که سه ستون وزنه دارد — ستونِ id سنگین‌تر از ستونِ class و آن هم سنگین‌تر از ستونِ نوع — و ترازو به سمتِ id خم شده

آخر این فصل می‌توانی:

  • بگویی وقتی چند قاعده به یک عنصر می‌خورند، cascade چطور برنده را انتخاب می‌کند
  • specificity یک selector را بشماری و از قبل بگویی کدام قاعده می‌بَرد
  • توضیح بدهی inheritance چیست و چرا color ارث می‌رسد ولی background-color نه
  • بگویی چرا !important جوابِ درست نیست

قبل از شروع#

  • از فصل ۲ این ترم: selectorِ نوع، class (با .) و id (با #). این فصل روی همین سه تا بنا می‌شود.
  • از فصل ۱ این ترم: color و background-color. باز هم همین دو property کافی‌اند.

فقط یک مرورگر لازم داری، یا همین آزمایشگاه.

۱. cascade: وقتی قاعده‌ها با هم می‌جنگند#

فرض کن این دو قاعده را نوشته‌ای که هر دو دقیقاً p را هدف گرفته‌اند:

p {
  color: gray;
}

p {
  color: teal;
}

هر دو یک عنصر را می‌خواهند رنگ کنند، با دو رنگِ متفاوت. مرورگر نمی‌تواند هم خاکستری باشد هم سبزآبی؛ باید یکی را انتخاب کند. این انتخاب‌کردن، همان cascade است: مجموعه قانون‌هایی که مرورگر با آن‌ها برندهٔ یک جنگِ قاعده‌ها را پیدا می‌کند.

اولین قانونِ cascade ساده است: وقتی دو قاعده دقیقاً هم‌وزن‌اند، آنکه پایین‌تر (دیرتر) نوشته شده برنده است. اینجا هر دو selector یکی‌اند (p)، پس هم‌وزن‌اند و قاعدهٔ پایینی می‌بَرد.

چک کن: پاراگراف‌ها سبزآبی می‌شوند، نه خاکستری. حالا جای دو قاعده را عوض کن — teal را ببر بالا، gray را بیاور پایین — و ببین رنگ به خاکستری برمی‌گردد. با چشمِ خودت دیدی که «قاعدهٔ دیرتر برنده است».

قبل از ادامه، جواب بده: اگر دو قاعدهٔ هم‌وزن، یکی رنگِ قرمز و دیگری رنگِ آبی بخواهند و قرمز پایین‌تر نوشته شده باشد، رنگ چه می‌شود؟


جواب: قرمز. چون هم‌وزن‌اند و قرمز دیرتر آمده. ولی این فقط نصفِ ماجراست — و نصفِ گمراه‌کننده‌اش. الان می‌بینی که «دیرتر برنده است» خیلی وقت‌ها اصلاً درست نیست.

۲. specificity: چرا «دیرتر» همیشه برنده نیست#

حالا یک خط داریم که هم id دارد هم class: <p id="intro" class="lead">. سه قاعده به آن می‌خورند، و عمداً قاعدهٔ p را آخر گذاشته‌ایم:

#intro {
  color: crimson;
}

.lead {
  color: teal;
}

p {
  color: gray;
}

بر اساسِ چیزی که همین الان یاد گرفتی، منطقی است فکر کنی چون p آخر از همه نوشته شده، برنده است و آن خط خاکستری می‌شود. نگاه کن به صفحه.

خاکستری نشد. قرمز شد. «دیرتر برنده است» اینجا کار نکرد — و این دقیقاً همان‌جایی است که تازه‌کارها ساعت‌ها سردرگم می‌شوند.

🔧 اگر کار نکرد: رنگی که می‌بینی با انتظارت فرق دارد؟ ترتیبِ نوشتن را فراموش کن؛ ترتیب فقط وقتی حرفِ آخر را می‌زند که قاعده‌ها هم‌وزن باشند. اینجا هم‌وزن نیستند. مرورگر اول وزنِ هر selector را می‌شمارد، و فقط اگر مساوی شدند سراغِ ترتیب می‌رود. پس بشمار.

به این وزن می‌گویند specificity. شمردنش راحت است: هر selector را در سه ستون می‌شماری — چند تا id، چند تا class، چند تا نوع:

selector id class نوع
#intro 1 0 0
.lead 0 1 0
p 0 0 1

حالا از چپ مقایسه می‌کنی، مثلِ مقایسهٔ عددهای چندرقمی که رقمِ سمتِ چپ مهم‌تر است. اول ستونِ id: فقط #intro آنجا یک ۱ دارد و بقیه صفرند. همین کافی است — #intro برنده است، مهم نیست کجای فایل نوشته شده. یک id از هر تعداد class و نوع سنگین‌تر است، و یک class از هر تعداد نوع.

این ترتیبِ اهمیت را به خاطر بسپار: id قوی‌تر از class، و class قوی‌تر از نوع. برای همین در فصل قبل گفتیم id برای استایل «زیادی سفت‌وسخت» است — وزنش آن‌قدر بالاست که بعداً به‌سختی می‌شود رویش را پوشاند.

قبل از ادامه، جواب بده: خطِ دومِ ما <p class="lead"> است (فقط class، بدونِ id). با همان سه قاعدهٔ بالا، این خط چه رنگی می‌شود؟


جواب: سبزآبی (teal). #intro به این خط نمی‌خورد چون این خط id="intro" ندارد. می‌ماند .lead (وزن 0,1,0) و p (وزن 0,0,1). ستونِ class را مقایسه کن: .lead یک ۱ دارد، p صفر. پس .lead می‌بَرد و خط سبزآبی می‌شود — باز هم بی‌اعتنا به اینکه p دیرتر نوشته شده.

۳. inheritance: رنگی که از بالا می‌آید#

یک قاعدهٔ دیگر به بازی اضافه کن، این‌بار روی body:

body {
  color: navy;
}

<body> والدِ همه‌چیز است. اگر برای امتحان، آن سه قاعدهٔ p و .lead و #intro را موقتاً پاک کنی و فقط همین بماند، می‌بینی همهٔ متن‌های صفحه سرمه‌ای می‌شوند — با اینکه هیچ قاعده‌ای مستقیم به آن پاراگراف‌ها نخورده. رنگ از body به فرزندانش ارث رسیده. اسمِ این پدیده inheritance است: بعضی property ها از والد به فرزند سرازیر می‌شوند.

یک درختِ عنصرها: رنگِ متن مثلِ جوهر از ریشه به همهٔ شاخه‌ها سرازیر شده، ولی کنارش پس‌زمینه در هر جعبه جدا و بی‌سرازیری مانده است

ولی همهٔ property ها این‌طور نیستند. color ارث می‌رسد؛ background-color نه. برای اینکه با چشم ببینی‌اش، این را در آزمایشگاه امتحان کن: به body یک background-color: gold بده، بعد به main یک background-color: cornsilk. یک بلوکِ کِرِمیِ جدا می‌بینی که روی صفحهٔ طلایی نشسته. اگر background-color ارث می‌رسید، main از قبل طلایی بود و اصلاً جدا دیده نمی‌شد. پس هر عنصر پس‌زمینهٔ خودش را دارد و پیش‌فرضش شفاف است؛ آن رنگِ طلاییِ صفحه فقط پس‌زمینهٔ body بود که از پشتِ عنصرهای شفاف دیده می‌شد، نه رنگی که ارث رسیده باشد.

یک نکتهٔ مهم: inheritance ضعیف‌ترین است. آن body { color: navy } به همهٔ متن‌ها ارث می‌رسد، ولی همین‌که یک قاعدهٔ مستقیم مثلِ p { color: gray } هم باشد، آن قاعده — هرچقدر هم کم‌وزن — بر رنگِ ارث‌رسیده غلبه می‌کند. رنگِ ارث‌رسیده فقط وقتی دیده می‌شود که هیچ قاعدهٔ مستقیمی روی آن عنصر نباشد.

قبل از ادامه، جواب بده: اگر روی body بنویسی color: navy و هیچ قاعدهٔ دیگری برای یک پاراگرافِ به‌خصوص نداشته باشی، آن پاراگراف چه رنگی است؟


جواب: سرمه‌ای. رنگ را از body ارث می‌برد، چون هیچ قاعدهٔ مستقیمی جلویش را نگرفته.

۴. inherit و initial: ارث را دستی کنترل کن#

گاهی می‌خواهی خودت بگویی «این عنصر رنگِ والدش را بگیرد». مقدارِ ویژه‌ای برای این هست: inherit. لینک‌ها مثالِ خوبی‌اند، چون مرورگر برایشان یک رنگِ پیش‌فرضِ آبی می‌گذارد که ارث را نادیده می‌گیرد. به قاعده‌هایت این را اضافه کن:

a {
  color: inherit;
}

چک کن: آن لینکِ وسطِ خطِ آخر، از رنگِ آبیِ پیش‌فرض درمی‌آید و هم‌رنگِ متنِ اطرافش می‌شود. با inherit صریح گفتی «رنگِ والدت را بگیر».

یک مقدارِ ویژهٔ دیگر هم هست: initial، که property را به مقدارِ کارخانه‌ای‌اش برمی‌گرداند (برای color یعنی سیاه). امتحانش کن: به یک پاراگراف color: initial بده و ببین با اینکه body سرمه‌ای گفته، آن پاراگراف سیاه می‌شود — انگار هیچ رنگی برایش تعیین نشده.

۵. چرا !important جواب نیست#

فرض کن می‌خواهی به‌زور آن خطِ #intro را خاکستری کنی، ولی هرچه می‌کنی قرمز می‌ماند چون id سنگین است. یک راهِ وسوسه‌انگیز هست: به قاعدهٔ p یک !important بچسبانی:

p {
  color: gray !important;
}

و... کار می‌کند. !important از کلِ بازیِ specificity می‌پرد و به‌زور برنده می‌شود. پس چرا نگوییم همه‌جا از آن استفاده کن؟

چون یک پتک است، نه یک ابزارِ دقیق. مشکل‌ها همین‌جا شروع می‌شوند: فردا که بخواهی همان خط را رنگِ دیگری کنی، specificityِ معمولی دیگر جواب نمی‌دهد — مجبوری یک !importantِ دیگر بزنی. و آن یکی را هم که خواستی بپوشانی، باز !importantِ بعدی. کم‌کم فایلت پر می‌شود از !important هایی که با هم می‌جنگند و دیگر هیچ‌کس نمی‌فهمد چه چیزی چه چیزی را می‌پوشاند. !important مشکلِ امروز را حل می‌کند و مشکلِ بزرگ‌ترِ فردا را می‌سازد.

جوابِ درست تقریباً همیشه این است: به‌جای زورزدن، specificity را بشمار و selectorَت را درست انتخاب کن. آن !important را از قاعدهٔ p بردار تا به فایلِ سالم برگردی. این فایلِ کاملِ styles.cssِ ماست:

/* styles.css */
body {
  color: navy;
}

p {
  color: gray;
}

.lead {
  color: teal;
}

#intro {
  color: crimson;
}

a {
  color: inherit;
}

و این HTMLِ کاملی است که این قاعده‌ها رویش اجرا می‌شوند:

<!-- index.html -->
<!DOCTYPE html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="UTF-8">
    <title>یادداشت‌های من</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <main>
      <p id="intro" class="lead">این خط هم id دارد هم class.</p>
      <p class="lead">این خط فقط class دارد.</p>
      <p>این یک خط کاملاً معمولی است.</p>
      <p>یک <a href="#">لینک</a> وسط یک خط عادی.</p>
    </main>
  </body>
</html>

سه رنگِ متفاوت روی سه خطِ اول می‌بینی — قرمز، سبزآبی، خاکستری — و همه از یک اصل درمی‌آیند. جالب اینجاست: آن body { color: navy } در نتیجهٔ نهایی هیچ‌جا دیده نمی‌شود، چون هر خط یک قاعدهٔ مستقیمِ خاص‌تر دارد که رنگِ ارث‌رسیده را می‌پوشاند. همین یک صفحه، هر سه مفهومِ فصل را کنارِ هم نشان می‌دهد: cascade انتخاب می‌کند، specificity وزن می‌کشد، و inheritance ضعیف‌ترینِ آن‌هاست.

🤖 با AI: وقتی یک رنگ آن‌طور که می‌خواهی اعمال نمی‌شود و از دستیار AI ات کمک می‌خواهی، خیلی وقت‌ها با اعتمادبه‌نفس می‌گوید «یک !important بزن، درست می‌شود». این همان «AI هم اشتباه می‌کند» است — جوابش کار می‌کند ولی درست نیست، و همان دردسری را می‌سازد که این بخش گفت. به‌جایش از AI بپرس «specificity این دو selector را برایم بشمار»، بعد خودت روی کاغذ هم بشمار و با هم بسنج. کمک‌گرفتن برای فهمیدن، نه برای پتک‌برداشتن.

واژه‌های تازهٔ این فصل#

کلمه تلفظ به حروف فارسی یعنی چه
cascade کَسکِید قانون‌هایی که مرورگر با آن‌ها بینِ قاعده‌های متضاد برنده را انتخاب می‌کند
specificity اِسپِسیفیسیتی وزنِ یک selector؛ با شمردنِ id و class و نوع به‌دست می‌آید
inheritance اینهِریتِنس سرازیرشدنِ بعضی property ها (مثل color) از والد به فرزند
inherit اینهِریت مقداری که می‌گوید «رنگِ والدت را بگیر»
initial اینیشِیال مقداری که property را به مقدارِ کارخانه‌ای‌اش برمی‌گرداند
important ایمپورتنت با !important قاعده به‌زور برنده می‌شود؛ آخرین راه، نه اولین

تمرین‌ها

اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.

در فصل بعد#

الان می‌دانی وقتی چند قاعده به یک عنصر می‌خورند چه اتفاقی می‌افتد — نه با حدس، با شمردن. این سنگِ بنای همه‌چیزِ CSS است. فصل بعد از رنگ فراتر می‌رویم و سراغِ جعبه می‌رویم: هر عنصر در CSS یک جعبه است با فضای داخلی، حاشیه و مرز. یاد می‌گیری این جعبه از چه لایه‌هایی ساخته شده و چطور اندازه و فاصله‌اش را کنترل کنی — همان چیزی که به آن box model می‌گویند.

به آخر این فصل رسیدی!

اگر ساختی و جواب داد، این دکمه مال توست.