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

فصل ۸ از ۱۱

پیشرفت ترم
۰٪

ترم ۶ · حرفه‌ای و واقعی

معماری CSS در مقیاس بزرگ

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

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

تا حالا CSSِ تو کوچک بوده و همه‌چیز سرِ جایش. ولی وقتی یک پروژه بزرگ می‌شود — ده‌ها component، صدها قاعده — دو مشکلِ تازه سراغت می‌آید که با «درست‌نوشتنِ» یک قاعده حل نمی‌شوند؛ با معماری حل می‌شوند. در این فصل یاد می‌گیری چطور CSS را در مقیاسِ بزرگ منظم نگه داری. آخرِ فصل یک کارتِ «حذف حساب» داری که دکمهٔ قرمزِ خطرش بدونِ جنگیدن قرمز می‌شود، و می‌فهمی چرا این مهم‌ترین مهارتِ CSSِ حرفه‌ای است. مفهومِ نحویِ تازه‌ای اینجا نیست — همه‌اش قضاوت است، روی چیزهایی که قبلاً بلدی.

دو مسیرِ کنار هم برای رنگ‌کردنِ یک دکمه؛ مسیرِ راست پلکانی بالا می‌رود و هر پله ضخیم‌تر از قبلی است تا به یک علامتِ توقفِ قرمزِ درشت برسد، مسیرِ چپ یک خطِ صاف و کوتاه و آرام است که مستقیم به دکمه می‌رسد

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

  • بگویی «جنگِ specificity» چیست و چرا به !important ختم می‌شود
  • selector ها را صاف و کم‌قدرت نگه داری و با @layer ترتیب را کنترل کنی
  • کلاس‌ها را با یک روشِ ثابت نام‌گذاری کنی تا به‌هم نریزند
  • فرقِ کلاس‌های utility و semantic و جای هرکدام را بفهمی

قبل از شروع#

  • از ترم ۲ فصل ۳: specificity و شمارشش — قلبِ این فصل همان است.
  • از فصل ۲ همین ترم: @layer — این‌بار می‌بینی چرا واقعاً نجاتت می‌دهد.

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

۱. وقتی CSS بزرگ می‌شود#

در یک فایلِ کوچک همه‌چیز ساده است. ولی هرچه بزرگ‌تر شود، دو چیز خراب می‌شود. اول، اسم‌ها به‌هم می‌ریزند: دو نفر (یا خودت در دو هفتهٔ مختلف) یک کلاس به‌نامِ .title می‌سازند برای دو چیزِ متفاوت، و روی هم می‌افتند. دوم، جنگِ specificity درمی‌گیرد: یک استایل باید اعمال شود ولی یک قاعدهٔ قوی‌ترِ دیگر جلویش را می‌گیرد، و تو برای بردن، selectorِ سنگین‌تر می‌نویسی. معماریِ CSS یعنی جلوگیری از این دو، نه رفع‌شان بعد از وقوع.

۲. جنگِ specificity#

بیا واقعی‌ترین علتِ این جنگ را ببینیم. فرض کن خواستی «همهٔ دکمه‌های داخلِ کارت» را آبی کنی، پس نوشتی .card .btn. جدا هم یک modifier داری که دکمهٔ خطر را قرمز کند:

.card .btn {
  background-color: var(--brand);
}

.btn--danger {
  background-color: var(--danger);
}

در آزمایشگاه نگاه کن: دکمهٔ «حذف حساب» با اینکه کلاسِ btn--danger دارد، آبی مانده، نه قرمز. چرا؟ چون .card .btn دو کلاس است (specificity بالاتر) و .btn--danger یک کلاس. قاعدهٔ قوی‌تر برنده می‌شود، هرچند بعدش نوشته شده باشد.

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

🔧 اگر کار نکرد: ریشهٔ مشکل این نیست که modifier ات ضعیف است؛ این است که قاعدهٔ پایه بیش‌ازحد قوی نوشته شده. راهِ حل صاف‌کردن است، نه سنگین‌ترکردن. آن .card .btn را به یک کلاسِ تنها تبدیل کن — .btn:

.btn {
  background-color: var(--brand);
}

.btn--danger {
  background-color: var(--danger);
}

چک کن: حالا دکمهٔ «حذف حساب» قرمز شد. هر دو قاعده یک کلاس‌اند (specificityِ برابر)، پس ساده‌ترین قانونِ cascade تصمیم می‌گیرد: آخری برنده است، و چون .btn--danger بعد از .btn آمده، قرمز می‌بَرد. جنگ تمام شد چون از اول درنگرفت.

۳. specificity را صاف و کم نگه دار#

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

ولی گاهی واقعاً می‌خواهی یک دسته قاعده همیشه بر یک دستهٔ دیگر بچربد — مثلاً استایلِ component همیشه بر استایلِ پایه. اینجا @layerِ فصل ۲ ابزارِ درست است: ترتیبِ برنده‌شدن را تو با ترتیبِ layer ها تعیین می‌کنی، نه با شمارشِ specificity. هر چیزی در یک layerِ بعدی، بر layerِ قبلی می‌بَرد — فارغ از اینکه کدام selector سنگین‌تر است:

@layer base, components;

@layer base {
  .btn {
    background-color: var(--brand);
  }
}

@layer components {
  .btn--danger {
    background-color: var(--danger);
  }
}

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

دو ورقهٔ نیمه‌شفافِ روی هم که یک دکمه را رنگ می‌کنند؛ ورقهٔ نازکِ رویی رنگِ قرمزش را روی دکمه می‌نشاند و بر ورقهٔ ضخیم‌ترِ آبیِ زیرین می‌چربد، تا نشان دهد لایهٔ بالاتر برنده است فارغ از ضخامتش

۴. نام‌گذاری در مقیاس: یک روشِ ثابت#

برای اینکه اسم‌ها به‌هم نریزند، حرفه‌ای‌ها یک الگوی ثابتِ نام‌گذاری دارند. یکی از پرکاربردترین‌ها این است: block__element--modifier. یعنی: اسمِ کلِ component، بعد دو زیرخط و اسمِ یک تکهٔ داخلش، بعد دو خط‌تیره و اسمِ یک حالت. همان چیزی که ناخودآگاه در ترم‌های قبل هم دیدی:

.card { }           /* block: khod-e component */
.card__title { }    /* element: yek tekke daxel-e an */
.card__actions { }  /* element-e digar */
.btn--danger { }    /* modifier: yek halat-e digar */

فایده‌اش دوتاست. اول، اسم‌ها یکتا می‌شوند: .card__title هیچ‌وقت با .post__title قاطی نمی‌شود. دوم — و اینجا به بخشِ قبل وصل می‌شود — هرکدام یک کلاسِ تنها هستند، پس specificity همیشه صاف و برابر می‌ماند و جنگی درنمی‌گیرد. یک روشِ نام‌گذاریِ خوب، خودش نصفِ معماری است.

۵. utility در برابر semantic#

دو مکتبِ فکری برای کلاس‌ها هست. کلاسِ semantic اسمِ کاری را می‌گوید که آن تکه می‌کند: .btn، .card. کلاسِ utility اسمِ یک استایلِ تکی را می‌گوید: .flex، .gap-2، یک کلاس برای هر کارِ کوچک، که در HTML رویِ هم می‌چینی‌شان.

هرکدام یک معامله است. semantic، HTML را تمیز نگه می‌دارد و component را یک‌جا قابلِ‌تغییر می‌کند، ولی باید همه‌چیز را نام‌گذاری کنی و CSS بزرگ می‌شود. utility، سریع و یکدست است و اصلاً نام‌گذاری نمی‌خواهد، ولی HTML شلوغ می‌شود و استایل توی markup پخش می‌شود. پروژه‌های واقعی معمولاً هر دو را قاطی می‌کنند: component برای چیزهای تکرارشونده، utility برای تنظیم‌های ریز.

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

۶. کد کامل#

index.html با نام‌گذاریِ منظم:

<!-- index.html -->
<!DOCTYPE html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>معماری CSS</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <main class="page">
      <article class="card">
        <h2 class="card__title">حذف حساب</h2>
        <p class="card__text">این کار برگشت‌پذیر نیست و همهٔ داده‌هایت پاک می‌شود.</p>
        <footer class="card__actions">
          <button class="btn btn--danger" type="button">حذف حساب</button>
          <button class="btn" type="button">انصراف</button>
        </footer>
      </article>
    </main>
  </body>
</html>

و styles.css با selector های صاف داخلِ layer های منظم:

/* styles.css */
* {
  box-sizing: border-box;
}

:root {
  --bg: #f6edda;
  --card: #ffffff;
  --ink: #2c2114;
  --muted: #5a5342;
  --brand: #2a6b93;
  --danger: #bc3e1c;
  --line: #d8ccae;
}

/* tartib-e layer-ha maloom ast: component bad az base miayad */
@layer base, components;

@layer base {
  body {
    margin: 0;
    font-family: Tahoma, sans-serif;
    background-color: var(--bg);
    color: var(--ink);
    padding: 40px;
    line-height: 1.9;
  }
}

@layer components {
  .card {
    max-width: 340px;
    margin-inline: auto;
    padding: 24px;
    background-color: var(--card);
    border: 1px solid var(--line);
    border-radius: 16px;
  }

  .card__title {
    margin: 0 0 6px;
    font-size: 1.3rem;
  }

  .card__text {
    margin: 0 0 18px;
    color: var(--muted);
  }

  .card__actions {
    display: flex;
    gap: 10px;
    margin: 0;
  }

  .btn {
    padding: 8px 18px;
    border: 1px solid var(--brand);
    border-radius: 10px;
    background-color: var(--brand);
    color: #ffffff;
    font-family: inherit;
    font-size: 1rem;
    font-weight: 700;
    cursor: pointer;
  }

  .btn--danger {
    border-color: var(--danger);
    background-color: var(--danger);
  }
}

چک کن: دو دکمه می‌بینی — «حذف حساب» قرمز و «انصراف» آبی — و هیچ selectorی بیش از یک کلاس نیست. هیچ !importantی هم لازم نشد. این شکلِ یک CSSِ سالم است: صاف، نام‌دار، و لایه‌بندی‌شده.

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

کلمه تلفظ به حروف فارسی یعنی چه
specificity war اِسپِسیفیسیتی وار چرخهٔ سنگین‌ترکردنِ selector ها که به !important ختم می‌شود
flat selector فلَت سِلِکتور selectorِ کم‌قدرت، ترجیحاً یک کلاسِ تنها
BEM بِم روشِ نام‌گذاریِ block__element--modifier تا اسم‌ها یکتا بمانند
utility class یوتیلیتی کلاس کلاسِ تک‌کاره مثلِ .flex که در HTML روی هم می‌چینی
semantic class سِمَنتیک کلاس کلاسی که اسمِ کارِ یک component را می‌گوید، مثلِ .btn

تمرین‌ها

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

در فصل بعد#

الان می‌توانی CSS را در مقیاسِ بزرگ منظم و بی‌جنگ نگه داری. ولی هرچقدر هم مرتب کار کنی، لحظه‌هایی پیش می‌آید که چیزی «باید» کار کند و نمی‌کند و نمی‌دانی چرا. فصل بعد مثلِ یک حرفه‌ای اشکال‌زدایی می‌کنیم: مسئله را نصف می‌کنی تا مقصر را گیر بیندازی، مقدارِ نهایی را در Computed می‌خوانی، و آن یک عنصری را که کلِ layout را به‌هم ریخته پیدا می‌کنی.

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

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