کدام عنصر روی کدام یکی قرار می‌گیرد: z-index و Stacking Context

در درس‌های قبل با position و رفتار جابه‌جایی عناصر آشنا شدیم. اما وقتی چند عنصر positioned (یعنی با position غیر از static) روی هم قرار می‌گیرن، یه سؤال جدید مطرح می‌شه: کدوم عنصر باید روی بقیه دیده بشه؟ این آخرین درس فصل دهمه و دقیقاً به همین سؤال، با کمک z-index و مفهوم Stacking Context، جواب می‌ده.

مشکل: وقتی عناصر روی هم می‌افتند

.box-a {
  position: absolute;
  top: 20px;
  left: 20px;
  background-color: coral;
}

.box-b {
  position: absolute;
  top: 40px;
  left: 40px;
  background-color: teal;
}
<div class="box-a">جعبه A</div>
<div class="box-b">جعبه B</div>

در این مثال، دو جعبه با هم تداخل بصری دارن. بدون هیچ Property اضافه‌ای، مرورگر بر اساس یه قانون پیش‌فرض ساده تصمیم می‌گیره: عنصری که دیرتر در HTML نوشته شده (یعنی از نظر Source Order، که در فصل ۲ دیدیم، پایین‌تره)، روی عنصر قبلی قرار می‌گیره. در این مثال، چون .box-b بعد از .box-a نوشته شده، روی اون قرار می‌گیره.

z-index: کنترل دستی ترتیب لایه‌ها

.box-a {
  position: absolute;
  z-index: 2;
}

.box-b {
  position: absolute;
  z-index: 1;
}

Property به اسم z-index اجازه می‌ده این ترتیب پیش‌فرض رو، مستقل از Source Order، دستی کنترل کنیم. عددی که بزرگ‌تره، روی عنصر با عدد کوچک‌تر قرار می‌گیره. در این مثال، حتی با اینکه .box-b بعد از .box-a در HTML اومده، چون z-index اون کوچک‌تره (۱ در برابر ۲)، این‌بار .box-a روی .box-b قرار می‌گیره.

شرط ضروری: z-index فقط روی عناصر Positioned اثر دارد

.box {
  z-index: 10; /* بدون position، هیچ اثری ندارد */
}

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

مقادیر منفی در z-index

.background-layer {
  position: relative;
  z-index: -1;
}

z-index می‌تونه عدد منفی هم بگیره؛ این کار باعث می‌شه عنصر، پشت عناصر دیگه‌ای که z-index‌شون صفر یا مثبته (یا حتی پشت عناصر عادی static که در همون Stacking Context هستن) قرار بگیره.

Stacking Context چیست؟

اینجا می‌رسیم به مفهومی که خیلی وقت‌ها باعث سردرگمی می‌شه: Stacking Context (زمینه چیدمان لایه‌ای). این مفهوم، شبیه چیزی‌ست که در فصل ۵ با Formatting Context دیدیم؛ یعنی صفحه، یه فضای یکپارچه و مسطح برای مقایسه همه z-index‌ها نیست، بلکه به بخش‌های مستقلی تقسیم می‌شه که هرکدوم، سیستم لایه‌بندی داخلی خودشون رو دارن.

چه چیزی یک Stacking Context جدید می‌سازد؟

یک عنصر، در شرایط مختلفی می‌تونه یک Stacking Context جدید بسازه؛ رایج‌ترین این شرایط عبارتن از:

  • عنصر ریشه سند (html)
  • عنصری با position غیر از static، به‌همراه یک z-index غیر از auto
  • عنصری با opacity کمتر از 1 (که در فصل ۵ دیدیم)
  • عنصری با transform، filter یا چند Property تخصصی دیگر (که در فصل‌های آینده کامل بررسی‌شون می‌کنیم)

چرا این مفهوم اهمیت عملی دارد؟

.parent-a {
  position: relative;
  z-index: 1;
}

.child-a {
  position: absolute;
  z-index: 999;
}

.parent-b {
  position: relative;
  z-index: 2;
}
<div class="parent-a">
  <div class="child-a">فرزند با z-index بسیار بزرگ</div>
</div>
<div class="parent-b">این عنصر روی فرزند بالا قرار می‌گیرد</div>

این مثال، شاید عجیب‌ترین و در عین حال آموزنده‌ترین رفتار z-index رو نشون می‌ده: با اینکه .child-a مقدار z-index: 999 داره (که خیلی بزرگ‌تر از همه‌ست)، همچنان پشت .parent-b قرار می‌گیره! دلیلش اینه که .child-a، چون فرزند .parent-aه، محدود به Stacking Context همون والدشه؛ یعنی z-index: 999 فقط در مقایسه با Siblingهای خودش داخل همون Stacking Context (یعنی داخل .parent-a) معنا داره، نه در مقایسه با کل صفحه. و چون خود .parent-a به‌طور کلی z-index: 1 داره که کمتر از z-index: 2 مربوط به .parent-bست، کل Stacking Context مربوط به .parent-a (شامل تمام فرزندانش، با هر z-index داخلی‌ای)، پشت .parent-b قرار می‌گیره.

یک قانون کلیدی: z-index فرزندان، از Stacking Context والد فراتر نمی‌رود

این دقیقاً همون درسیه که باید از مثال بالا گرفت: هر Stacking Context، مثل یه «جعبه بسته» عمل می‌کنه. مقایسه z-indexها، فقط داخل یک Stacking Context معنا داره؛ وقتی می‌خوایم بفهمیم دو عنصر از دو Stacking Context مختلف، کدوم روی کدوم قرار می‌گیره، دیگه به z-index داخلی فرزندان کاری نداریم، بلکه فقط z-index خود اون دو Stacking Context (یعنی همون والدهای اصلی‌شون) با هم مقایسه می‌شه.

یک مثال ساده‌تر برای جمع‌بندی

.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  z-index: 100;
}

.modal-box {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 101;
}

.site-header {
  position: fixed;
  top: 0;
  z-index: 10;
}

در این مثال، عدد z-index‌ها به‌گونه‌ای انتخاب شدن که Overlay و Modal (که باید روی همه‌چیز، حتی هدر ثابت سایت، دیده بشن)، بالاترین اعداد رو دارن؛ این تکنیک برای طراحی پنجره‌های Modal، بسیار رایجه.

نکات مهم

  • z-index فقط روی عناصری اثر دارد که position آن‌ها غیر از static باشد.
  • عدد بزرگ‌تر در z-index، به‌طور کلی روی عدد کوچک‌تر قرار می‌گیرد، ولی این مقایسه فقط داخل یک Stacking Context معتبر است.
  • Propertyهایی مثل opacity کمتر از ۱ یا transform، می‌توانند یک Stacking Context جدید بسازند، حتی بدون تعریف صریح z-index.

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

  • تعریف z-index روی عنصری با position: static، که هیچ تأثیری نخواهد داشت.
  • تصور اینکه یک z-index بسیار بزرگ (مثل 9999) روی یک فرزند، همیشه تضمین می‌کند آن عنصر بالاتر از همه‌چیز در صفحه دیده شود؛ در حالی که Stacking Context والد می‌تواند این را محدود کند.
  • استفاده بی‌نظم و تصادفی از اعداد z-index در سراسر پروژه، بدون یک سیستم منسجم (مثلاً لایه‌های مشخص برای هدر، Dropdown، Modal)، که مدیریت آن را در پروژه‌های بزرگ دشوار می‌کند.

جمع‌بندی

z-index ترتیب نمایش لایه‌ای عناصر positioned را کنترل می‌کند؛ عدد بزرگ‌تر روی عدد کوچک‌تر قرار می‌گیرد. اما این مقایسه، محدود به یک Stacking Context است؛ مفهومی که با شرایطی مثل position همراه با z-index، opacity کمتر از ۱، یا transform ایجاد می‌شود. فرزندان یک Stacking Context، هرچقدر هم z-index بزرگی داشته باشند، هرگز نمی‌توانند از محدوده Stacking Context والدشان در مقایسه با عناصر بیرونی فراتر بروند؛ این نکته، پایان فصل دهم و یکی از ظریف‌ترین و مهم‌ترین مفاهیم Positioning در CSS است.