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

فصل ۹ از ۱۱

پیشرفت ترم
۰٪

ترم ۳ · Layout: چیدمان

z-index و stacking context

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

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

تا اینجا با position یاد گرفتی چیزها را روی هم بیندازی. ولی وقتی دو چیز روی هم می‌افتند، یک سؤالِ تازه پیش می‌آید: کدام جلوتر است؟ کدام روی آن یکی می‌نشیند و کدام پشتش پنهان می‌شود؟ این فصل جوابِ همان سؤال است. و مثلِ فصلِ cascade، اینجا هم رمزِ کار این است که حدس نزنی، بشماری — چون این موضوع معروف‌ترین دامِ کلِ layout است: جایی که یک عددِ بزرگ‌تر می‌گذاری و باز آن چیز پشت می‌ماند، و ساعت‌ها گیج می‌شوی که چرا.

چند لایهٔ رنگی که روی هم افتاده‌اند و در عمق چیده شده‌اند، لایهٔ جلویی کاملاً روی بقیه نشسته و ترتیبِ جلو-به-عقب پیداست

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

  • با z-index تعیین کنی کدام عنصر جلوتر بنشیند
  • بگویی چرا z-index گاهی هیچ کاری نمی‌کند
  • یک stacking context را بشماری و از قبل بگویی کدام لایه برنده است
  • با overflow تعیین کنی محتوایی که از جعبه بیرون می‌زند چه شود

قبل از شروع#

  • از فصل ۸ این ترم: position و مقدارهایش (relative, absolute)، و اینکه یک عنصرِ positioned از جریانِ عادی جدا می‌شود و می‌تواند روی بقیه بیفتد.
  • از فصل cascade (ترم ۲): همان طرزِ فکر — وقتی چند چیز با هم می‌جنگند، مرورگر با یک قانونِ مشخص برنده را انتخاب می‌کند، و تو می‌توانی از قبل بشماری‌اش.

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

۱. وقتی دو چیز روی هم می‌افتند#

از فصل ۸ یادت هست که یک عنصرِ position: absolute از جریان بیرون می‌آید و می‌تواند روی عنصرِ دیگری بیفتد. در آزمایشگاه دو جعبه داریم که همین‌طورند — A و B که هر دو absolute اند و هم‌پوشانی دارند:

.box {
  position: absolute;
  width: 120px;
  height: 120px;
  padding: 10px;
  color: #ffffff;
  font-weight: 700;
}

.box-a {
  top: 0;
  right: 0;
  background-color: #2a6b93;
}

.box-b {
  top: 30px;
  right: 60px;
  background-color: #bc3e1c;
}

به صفحه نگاه کن: جعبهٔ B (قرمز) روی جعبهٔ A (آبی) نشسته. هیچ‌جا نگفتیم کدام جلو باشد، پس مرورگر یک قانونِ ساده دارد: وقتی هیچ دستوری ندادی، هرچه در HTML دیرتر آمده جلوتر می‌نشیند. B بعد از A نوشته شده، پس B جلوست. همان منطقِ «دیرتر برنده است» که در cascade دیدی، این‌بار برای لایه‌ها.

قبل از ادامه، جواب بده: اگر در HTML جای A و B را عوض کنی (اول B، بعد A)، کدام جلو می‌آید؟


جواب: A. چون حالا A دیرتر نوشته شده. تا وقتی هیچ z-index ای در کار نباشد، فقط ترتیبِ نوشتن تصمیم می‌گیرد.

۲. z-index: خودت ترتیب را تعیین کن#

نمی‌خواهیم اسیرِ ترتیبِ نوشتن باشیم. property ‌ای هست به‌نامِ z-index که با یک عدد می‌گوید هر عنصر چقدر جلو باشد: عددِ بزرگ‌تر، جلوتر. بیا A را — با اینکه اول نوشته شده — بیاوریم جلو. به .box-a یک z-index بده:

.box-a {
  top: 0;
  right: 0;
  background-color: #2a6b93;
  z-index: 1;
}

چک کن: حالا جعبهٔ A (آبی) آمد جلو و روی B نشست — با اینکه در HTML اول نوشته شده. عددِ 1 از پیش‌فرضِ B (که z-index ندارد و مثلِ صفر حساب می‌شود) بزرگ‌تر است، پس A بُرد. ترتیبِ نوشتن دیگر حرفِ آخر را نزد؛ z-index زد.

ولی یک شرطِ مهم هست که خیلی‌ها رویش می‌لغزند: z-index فقط روی عنصری کار می‌کند که positioned باشد — یعنی position اش چیزی غیر از staticِ پیش‌فرض باشد (relative, absolute, fixed, sticky). روی یک عنصرِ معمولیِ static، هر z-index ای بگذاری بی‌اثر است. جعبه‌های ما absolute اند، برای همین z-index رویشان کار کرد.

💡 نکته: لازم نیست عددها پشتِ‌سرِ هم باشند (۱، ۲، ۳). خیلی‌ها فاصله می‌گذارند (۱۰، ۲۰، ۳۰) تا بعداً بشود چیزی را وسطشان جا داد. فقط بزرگی و کوچکی‌شان مهم است، نه فاصله‌شان.

قبل از ادامه، جواب بده: یک عنصرِ <div>ِ معمولی (بدونِ position) داری و بهش z-index: 999 می‌دهی. جلو می‌آید؟


جواب: نه. z-index روی عنصری که positioned نیست هیچ کاری نمی‌کند. اول باید position اش را عوض کنی (مثلاً relative)، بعد z-index معنا پیدا می‌کند. این خودش اولین جای رایجِ «z-index کار نمی‌کند» است.

۳. دامِ اصلی: stacking context#

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

.card {
  position: absolute;
  z-index: 1;
}

.badge {
  position: absolute;
  z-index: 9999;
}

.ribbon {
  position: absolute;
  z-index: 2;
}

منطقی است فکر کنی badge با 9999 باید روی همه‌چیز بنشیند — از ribbon که فقط 2 دارد که خیلی بزرگ‌تر است. ولی به صفحه نگاه کن: ribbon روی badge افتاده. آن 9999 انگار اصلاً دیده نشد.

🔧 اگر کار نکرد: عددِ بزرگ را دیدی و باز عنصر پشت ماند؟ اینجا هیچ عددی را بزرگ‌تر نکن — این دام با زیاد کردنِ عدد بدتر می‌شود، نه بهتر. مشکل جای دیگری است: badge داخلِ card است، و card خودش یک z-index دارد (1). وقتی یک عنصرِ positioned یک z-index می‌گیرد، دورِ فرزندانش یک stacking context می‌سازد — یک جعبهٔ دربسته که عددهای داخلش فقط نسبت به هم معنا دارند، نه نسبت به دنیای بیرون. پس 9999ِ badge فقط می‌گوید «داخلِ card از همه جلوترم»، نه «از کلِ صفحه جلوترم». وقتی می‌خواهی card را با چیزهای بیرون بسنجی، عددی که مهم است z-indexِ خودِ card است (1)، نه 9999ِ بچه‌اش. و 1 از 2ِ ribbon کوچک‌تر است — پس کلِ card، با هر چه رویش هست، می‌رود پشتِ ribbon.

بیایید مثلِ cascade بشماریم. سه لایهٔ بیرونی داریم که مستقیماً با هم مقایسه می‌شوند:

عنصر context اش کجاست عددی که در آن context حساب می‌شود
card صفحهٔ اصلی 1
ribbon صفحهٔ اصلی 2
badge داخلِ context ِ card 9999 (ولی فقط داخلِ card)

در صفحهٔ اصلی، فقط card (با ۱) و ribbon (با ۲) رو‌در‌روی هم‌اند؛ ribbon می‌بَرد و جلو می‌نشیند. badge اصلاً واردِ این مقایسه نمی‌شود، چون در یک جعبهٔ دیگر زندگی می‌کند. 9999 عددِ بزرگی است، ولی در یک اتاقِ اشتباه فریاد می‌زند.

یک قابِ بزرگ که داخلش یک کارتِ کوچک با یک ستونِ بلند از نقطه‌ها (عددِ بزرگ) نشسته، ولی یک کارتِ بیرونی با ستونِ کوتاه‌ترِ نقطه‌ها جلوترِ آن قاب افتاده — نشان می‌دهد عددِ بزرگِ داخلِ قاب به بیرونش کاری ندارد

پس چطور درستش کنیم؟ نه با دست‌بردن به badge. باید z-indexِ خودِ card را از 2ِ ribbon بزرگ‌تر کنیم تا کلِ کارت (و همراهش badge) بیاید جلو:

.card {
  position: absolute;
  z-index: 3;
}

چک کن: حالا کارت و badge اش با هم آمدند روی ribbon. مشکل را با فهمیدنِ context حل کردی، نه با بزرگ‌تر کردنِ یک عددِ بی‌فایده. این همان درسِ فصل است: وقتی z-index کار نمی‌کند، عدد را زیاد نکن — بشمار ببین هر عنصر در کدام context است.

⚠️ مواظب باش: فقط z-index نیست که stacking context می‌سازد. چند property ِ دیگر هم بی‌سروصدا همین کار را می‌کنند — مهم‌ترینشان opacity ِ کمتر از ۱ و transform (که در ترم ۵ می‌بینی). برای همین گاهی یک عنصر پشت می‌ماند و تو اصلاً z-index ای در کار نمی‌بینی که مقصر باشد؛ مقصر یک opacity: 0.9ِ روی والد است که یک context ِ تازه ساخته. اولین سؤال همیشه این باشد: «این عنصر داخلِ context ِ کیست؟»

۴. overflow: وقتی محتوا از جعبه بیرون می‌زند#

یک موضوعِ دیگر که به چیدمان و لایه‌ها مربوط است: چه می‌شود وقتی محتوا از اندازهٔ جعبه‌اش بزرگ‌تر است؟ در آزمایشگاه یک جعبه داریم که قدِ ثابتِ 90px دارد ولی متنش بلندتر است:

.frame {
  height: 90px;
  padding: 12px;
  background-color: #cfe3f3;
  overflow: visible;
}

با overflow: visible — که مقدارِ پیش‌فرض است — متن از پایینِ جعبه بیرون می‌زند و روی چیزهای زیرش می‌ریزد. جعبه قدش 90px مانده، ولی محتوا به مرزش اعتنا نکرده. overflow همین را کنترل می‌کند: با محتوایی که جا نمی‌شود چه کند. چهار مقدارِ اصلی دارد:

  • visible — پیش‌فرض؛ محتوا بیرون می‌زند و دیده می‌شود.
  • hidden — هرچه از مرز بیرون بزند بریده می‌شود و دیگر دیده نمی‌شود.
  • scroll — محتوا داخل می‌ماند و همیشه یک نوارِ پیمایش (scrollbar) می‌گذارد.
  • auto — مثلِ scroll، ولی نوارِ پیمایش فقط وقتی می‌آید که واقعاً لازم باشد.

بیا auto را امتحان کنیم که پرکاربردترین است:

.frame {
  height: 90px;
  padding: 12px;
  background-color: #cfe3f3;
  overflow: auto;
}

چک کن: حالا متن دیگر بیرون نمی‌زند؛ داخلِ جعبه می‌ماند و یک نوارِ پیمایشِ کوچک کنارش آمده که می‌توانی متن را بالا-پایین کنی. مقدار را به hidden عوض کن و ببین نوار می‌رود و ته‌متن بریده می‌شود؛ به visible برگردان و ببین دوباره بیرون می‌ریزد. یک property، چهار رفتار.

قبل از ادامه، جواب بده: می‌خواهی محتوای اضافه دیده نشود ولی نوارِ پیمایش هم نیاید. کدام مقدار؟


جواب: hidden. محتوای بیرون‌زده را می‌بُرد و هیچ نواری نمی‌گذارد. (auto هم پنهانش می‌کند ولی نوارِ پیمایش می‌آورد تا بشود دیدش.)

⚠️ مواظب باش: overflow: hidden روی یک والد، هر چیزی را که از مرزِ آن والد بیرون بزند می‌بُرد — حتی یک عنصرِ positioned که عمداً بیرون گذاشته‌ای، یا یک sticky که می‌خواهد بچسبد. اگر یک روز چیزی که باید بیرونِ جعبه دیده شود ناپدید شد، یک overflow: hiddenِ روی یکی از والدها را مظنون بدان.

کد کاملِ این فصل#

این هم دو فایلِ کامل. اول index.html:

<!-- index.html -->
<!DOCTYPE html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="UTF-8">
    <title>z-index و لایه‌ها</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <section class="stage">
      <div class="box box-a">A</div>
      <div class="box box-b">B</div>
    </section>

    <section class="panel">
      <article class="card">
        <span class="badge">تازه</span>
        <p>کارت پروفایل</p>
      </article>
      <div class="ribbon">نوار اعلان</div>
    </section>

    <section class="frame">
      <p>این جعبه قدِ ثابتی دارد ولی متنش بلند است، پس متن از پایین بیرون می‌زند. خطِ دوم اینجاست. خطِ سوم هم می‌آید. خطِ چهارم، تا از مرزِ جعبه رد شود و ببینی سرریز چطور رفتار می‌کند.</p>
    </section>
  </body>
</html>

و styles.css — در حالتِ درستش، با z-indexِ card که مشکلِ بخش ۳ را حل کرده و overflow: autoِ بخش ۴:

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

body {
  font-family: Tahoma, sans-serif;
  background-color: #f6edda;
  color: #2c2114;
  padding: 24px;
}

.stage {
  position: relative;
  height: 160px;
  margin-bottom: 32px;
}

.box {
  position: absolute;
  width: 120px;
  height: 120px;
  padding: 10px;
  color: #ffffff;
  font-weight: 700;
}

.box-a {
  top: 0;
  right: 0;
  background-color: #2a6b93;
  z-index: 1;
}

.box-b {
  top: 30px;
  right: 60px;
  background-color: #bc3e1c;
}

.panel {
  position: relative;
  height: 190px;
  margin-bottom: 32px;
}

.card {
  position: absolute;
  top: 30px;
  right: 40px;
  width: 200px;
  height: 120px;
  padding: 12px;
  background-color: #ffffff;
  border: 2px solid #2c2114;
  z-index: 3;
}

.badge {
  position: absolute;
  top: -14px;
  right: -14px;
  padding: 4px 10px;
  background-color: #55731b;
  color: #ffffff;
  border-radius: 999px;
  z-index: 9999;
}

.ribbon {
  position: absolute;
  top: 10px;
  right: 0;
  width: 260px;
  padding: 10px;
  background-color: #a36207;
  color: #ffffff;
  z-index: 2;
}

.frame {
  height: 90px;
  padding: 12px;
  background-color: #cfe3f3;
  overflow: auto;
}

🤖 با AI: یک صفحه بساز که یک منو یا کارت پشتِ چیزِ دیگری گیر کرده و از دستیار AI ات بپرس «چرا z-indexِ من کار نمی‌کند؟». خیلی وقت‌ها اولین جوابش این است: «عددِ z-index را بزرگ‌تر کن». این دقیقاً همان «AI هم اشتباه می‌کند» است — جوابی که مشکل را حل نمی‌کند و تو را عمیق‌ترِ همان دام می‌برد. به‌جایش بپرس «کدام والدِ این عنصر یک stacking context ساخته؟» و خودت در DevTools والدها را نگاه کن و context ها را بشمار. کمک‌گرفتن برای فهمیدن، نه برای بزرگ‌تر کردنِ کورکورانهٔ یک عدد.

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

کلمه تلفظ به حروف فارسی یعنی چه
z-index زِد-ایندِکس عددی که تعیین می‌کند یک عنصرِ positioned چقدر جلو بنشیند؛ بزرگ‌تر یعنی جلوتر
stacking context اِستَکینگ کانتِکست جعبهٔ دربستهٔ لایه‌بندی؛ z-indexهای داخلش فقط نسبت به هم معنا دارند، نه نسبت به بیرون
positioned پوزیشِند عنصری که position اش static نیست؛ فقط این‌ها z-index می‌پذیرند
overflow اورفلو تعیین می‌کند محتوایی که از مرزِ جعبه بیرون می‌زند چه شود
scrollbar اِسکرول‌بار نوارِ پیمایش که می‌گذارد محتوای جا‌نشده را بالا-پایین کنی

تمرین‌ها

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

در فصل بعد#

الان می‌دانی لایه‌ها چطور روی هم می‌نشینند و چرا z-index گاهی کار نمی‌کند — نه با حدس، با شمردنِ context. یک نکتهٔ کوچک را هم دیدی: در همهٔ کدهای این فصل از top و right استفاده کردیم. ولی صفحهٔ ما راست‌به‌چپ است؛ آیا right همیشه انتخابِ درستی است، یا اگر یک روز صفحه چپ‌به‌راست شود خراب می‌شود؟ فصل بعد سراغِ همین می‌رویم: logical properties — راهی که به‌جای «چپ» و «راست»، به «شروع» و «پایانِ» خط فکر می‌کند و خودش با جهتِ صفحه هماهنگ می‌شود.

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

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