بررسی کامل z-index در CSS

آفلاین
user-avatar
عرفان حشمتی
31 خرداد 1400, خواندن در 15 دقیقه

در CSS ابزاری در اختیار ما قرار گرفته که ترتیب قرارگیری عناصر HTML بر روی یکدیگر را کنترل می‌کند و آن z-index است. عناصر با مقدار بیشتر در بالا قرار می‌گیرند:

از آنجا که first.box. دارای z-index بزرگتری نسبت به second.box. است، در جلو قرار می‌گیرد و اگر مقدار z-index را حذف کنیم، به عقب عنصر دیگر می‌رود.

با این حال همه چیز همیشه خیلی ساده نیست. بعضی اوقات مقدار z-index بزرگتر ممکن است در زیر قرار بگیرد.

آنچه در زیر اتفاق می‌افتد را بررسی کنید:

tooltip. دارای z-index بسیار بزرگتری نسبت به هدر است. پس چرا زیر آن قرار گرفته است؟

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

مخاطبان هدف

این آموزش برای توسعه دهندگان فرانت-اند در هر سطحی از تجربه نوشته شده است. به خصوص افرادی که قبلا با مشکلات z-index دست و پنجه نرم کرده‌اند.

Layer و Group

اگر تا به حال از نرم‌افزارهای ویرایش تصویر مانند فتوشاپ یا فیگما استفاده کرده‌اید، احتمالا با مفهوم لایه‌ها آشنا هستید:

تصویر ما 3 لایه جداگانه دارد که روی هم چیده شده‌اند. لایه پایین یک عکس گربه است، با 2 لایه در بالا که جزئیاتی را اضافه می‌کند. با قراردادن این لایه‌ها بر روی هم، با ترکیب نهایی زیر روبه رو می‌شویم:

در این برنامه می‌توانیم لایه‌ها را نیز گروه بندی کنیم:

مانند یک فایل در یک پوشه، گروه‌ها به ما اجازه می‌دهند تا لایه‌های خود را تقسیم کنیم. از نظر ترتیب قرارگیری، لایه‌ها مجاز به درهم آمیخته شدن بین گروه‌ها نیستند. به این صورت که تمام لایه‌های سگ در بالای همه لایه‌های گربه ظاهر می‌شوند.

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

در مبحث CSS هم همه چیز به روشی مشابه کار می‌کند و عناصر در پس زمینه‌هایی گروه بندی می‌شوند. وقتی به یک عنصر z-index می‌دهیم، این مقدار فقط در مقایسه با سایر عناصر در همان زمینه مقایسه می‌شود و این مقادیر گلوبال نیستند.

به طور پیش فرض، یک سند HTML ساده دارای یک زمینه کلی است که همه تگ‌ها را در بر می‌گیرد. اما می‌توانیم زمینه‌های اضافی نیز ایجاد کنیم.

روش‌های زیادی برای ایجاد زمینه وجود دارد، اما در اینجا معمول‌ترین آنها ذکر شده است:

.some-element {
  position: relative;
  z-index: 1;
}

با ترکیب این دو نوعی مکانیزم مخفی ایجاد شده و یک زمینه ساخته می‌شود، همچنین همه عناصر اطراف آن به همراه فرزندانش را دربرمی‌گیرد.

بیایید نگاهی دوباره به مشکل خود بیندازیم:

<style>
  header {
    position: relative;
    z-index: 2;
  }
  .tooltip {
    position: absolute;
    z-index: 999999;
  }
  main {
    position: relative;
    z-index: 1;
  }
</style>
<header>
  My Cool Site
</header>
<main>
  <div class="tooltip">
    A tooltip
  </div>
  <p>Some main content</p>
</main>

ما می‌توانیم زمینه‌های ایجاد شده در این قطعه را ترسیم کنیم:

عنصر tooltip. دارای z-index 999999 است، اما این مقدار فقط به <main> وابسته است. این نشان می‌دهد که tooltip در بالا یا پایین تگ <p> مجاور قرار می‌گیرد، نه چیز دیگر.

در همین حال در زمینه والد، <header> و <main> مقایسه می‌شوند. از آنجا که <main> از z-index کوچکتری برخوردار است، در زیر <header> نشان داده می‌شود. همه فرزندانش نیز تحت تاثیر قرار می‌گیرند. چیزی شبیه به شماره گذاری ورژن در نرم‌افزارها.

حدس می‌زنم که همه با نرم‌افزارهایی مانند Photoshop / Figma / Sketch تجربه کار ندارند. اگر تشبیه بالا را درست متوجه نشدید، با ذکر مثال آن را برایتان بیان می‌کنم:

در شماره گزاری نسخه‌های مختلف یک نرم‌افزار، اعداد ورژن‌ها با نقطه از هم جدا می‌شوند. به عنوان مثال نسخه 2.0 یک بسته نسخه بزرگتر از 1.0 است، همچنین یک نسخه بزرگتر از 1.999.

z-indexها هم مانند اعداد ورژن‌ها و زمینه‌ها مانند ردیف شماره گزاری‌ها عمل می‌کنند. هر بار که یک زمینه جدید ایجاد می‌شود، یک نقطه به نسخه قبلی اضافه می‌گردد:

<header> <!-- 2.0 -->
  My Cool Site
</header>
<main> <!-- 1.0 -->
  <div class="tooltip"> <!-- 1.999999 -->
    A tooltip
  </div>
</main>

tooltip ما در زیر <header> نشان داده می‌شود، زیرا 1.999999 نسخه پایین تری از 2.0 است. مهم نیست که چه تعداد 9 را به نسخه جزئی اضافه کنیم، این هرگز بر نسخه اصلی بزرگتر غلبه نمی‌کند.

حل مشکل

چگونه می‌توانیم مشکل tooltip خود را برطرف کنیم؟ در این حالت ما نیازی به ایجاد یک زمینه روی <main> نداریم:

بدون z-index، <main> زمینهای را ایجاد نمی‌کند. بنابراین سلسله مراتب ما به این شکل است:

از آنجا که header و tooltip اکنون در یک زمینه قرار دارند، مقادیر z-index آنها ملاک می‌باشد و در نتیجه tooltip به عنوان برنده ظاهر می‌شود.

یک نکته مهم: ما در اینجا در مورد روابط والد / فرزند صحبت نمی‌کنیم و مهم نیست که tooltip داخل header قرار دارد. بلکه مرورگر فقط به چیدمان زمینه‌ها اهمیت می‌دهد.

قانون شکنی

در این مثال می‌توانیم z-index را از <main> حذف کنیم، زیرا در واقع هیچ کاری انجام نمی‌دهد. اما اگر واقعا به منظور ایجاد یک زمینه برای <main> به z-index نیاز داشتیم چه؟

طبق قوانین CSS، راهی برای خلاص شدن از زمینه وجود ندارد. عنصری که درون یک زمینه قرار گرفته، هرگز قابل مقایسه با عناصر موجود در زمینه‌های دیگر نیست.

با این وجود با کمی تفکر هنوز هم می‌توانیم به نتیجه مطلوب برسیم.

می‌توانیم tooltip خود را خارج از <main> با افزودن آن به تگ <body> رندر کنیم. سپس می‌توانیم از خصوصیات CSS استفاده کنیم تا متناسب با آن موقعیت بندی شود، طوری که به نظر برسد فرزند آن عنصر است.

این یک تکنیک پیشرفته است و به برنامه ریزی دقیق نیاز دارد تا اطمینان حاصل شود که ما به طور ناخواسته تجربه استفاده از کیبورد برای پیمایش را برای افراد از بین نمی‌بریم. خوشبختانه کتابخانه‌هایی مانند Reach UI از این تکنیک استفاده کرده و همه این چالش‌ها را حل می‌کنند.

این بحث از موضوع آموزش ما خارج است، اما اگر علاقه مند به یادگیری بیشتر هستید، در مورد نحوه کار پورتال‌ها در ری‌اکت تحقیق کنید و کد منبع Reach UI را بررسی کنید.

ایجاد Stacking Context

دیدیم که چگونه می‌توانیم با ترکیب موقعیت یابی relative یا absolute با z-index زمینه‌هایی ایجاد کنیم اما این تنها راه نیست. در اینجا برخی روش‌های دیگر وجود دارد:

  • تنظیم opacity روی مقدار کمتر از 1
  • تنظیم position بر روی fixed یا sticky (برای این مقادیر به z-index نیازی نیست)
  • استفاده از mix-blend-mode به جای normal
  • افزودن z-index به یک فرزند داخلی با display: flex یا displau: grid
  • با استفاده از transform، filter، clip-path یا perspective
  • استفاده از will-change با مقداری مانند opacity یا transform
  • ایجاد یک زمینه با isolation: isolate (بیشتر این را بررسی خواهیم کرد)

چند راه دیگر نیز وجود دارد. لیست کامل را می‌توانید در MDN پیدا کنید.

این روش‌ها می‌توانند منجر به برخی شرایط شگفت آور شوند. آنچه در اینجا اتفاق می‌افتد را بررسی کنید:

main دیگر z-index را تنظیم نمی‌کند، در عوض از will-change استفاده می‌کند. ویژگی‌ای که می‌تواند به تنهایی موجب به وجود آمدن یک زمینه شود.

یک تصور اشتباه رایج در مورد z-index

برای اینکه z-index به خوبی کار کند، باید موقعیت را relative یا absolute قرار دهیم، آیا این درست است؟

نه کاملا. آنچه در اینجا اتفاق می‌افتد را بررسی کنید:

باکس دوم با استفاده از z-index بالاتر از عناصر فرزند دیگر قرار می‌گیرد. هرچند هیچ تعریف موقعیتی در قطعه وجود ندارد.

به طور کلی z-index فقط با عناصر "positioned" کار می‌کند (عناصری که موقعیت را به جای "static" پیش فرض تنظیم می‌کنند). اما مشخصات Flexbox یک استثنا را اضافه می‌کند: فرزندان فلکس می‌توانند از z-index استفاده کنند حتی اگر موقعیتشان static باشد.

قبل‌تر گفته شد که تمام عناصری که زمینه را ایجاد می‌کنند می‌توانند از z-index استفاده کنند، اما دیدیم که این نادرست است.

در اینجا یک چیز عجیب وجود دارد و فکر می‌کنم ارزش چند دقیقه وقت بررسی را داشته باشد.

در مثال فتوشاپ ما، یک تمایز مشخص بین گروه‌ها و لایه‌ها وجود دارد. همه عناصر لایه‌ای هستند و می‌توان گروه‌ها را به عنوان کمک کننده ساختاری برای مهار آنها ترکیب كرد.

با این حال، در وب این تمایز کمتر مشخص است. هر عنصری که از z-index استفاده می‌کند، باید زمینهای را نیز ایجاد کند.

وقتی تصمیم می‌گیریم به یک عنصر z-index بدهیم، هدف ما معمولا بردن آن عنصر به بالا یا زیر برخی از عناصر دیگر در زمینه والد است و ما قصد نداریم یک زمینه روی آن عنصر تولید کنیم. اما مهم است که آن را در نظر بگیریم.

وقتی یک زمینه ایجاد می‌شود، تمام فرزندان خود را "flat" می‌کند. این عناصر فرزند هنوز هم می‌توانند به صورت داخلی مرتب شوند، اما اساسا آنها را در داخل حبس کرده‌ایم.

بیایید نگاهی دیگر به مثال قبلی بیندازیم:

<header>
  My Cool Site
</header>
<main>
  <div class="tooltip">
    A tooltip
  </div>
  <p>Some main content</p>
</main>

به طور پیش فرض، عناصر HTML بر اساس ترتیب DOM قرار می‌گیرند و بدون هیچ گونه تداخل CSS، main در بالای header رندر می‌شود.

ما می‌توانیم با دادن z-index به header آن را در فوق قرار دهیم، البته بدون این که همه فرزندان آن را flat کنیم. این مکانیزم همان مشکلی است که قبلا در مورد آن بحث کردیم.

ما نباید به z-index صرفا به عنوان راهی برای تغییر ترتیب یک عنصر نگاه کنیم. بلکه باید آن را راهی برای تشکیل یک گروه پیرامون فرزندان آن عنصر بدانیم. z-index کار نمی‌کند مگر اینکه گروهی تشکیل شود.

چه قبول کنید چه نکنید، این چیز خوبی است

همانطور که در مثال tooltip خود مشاهده کردیم، چیدمان زمینه‌ها می‌تواند اشکالاتی ظریف و پیچیده را به وجود آورد. آیا بهتر نیست که مقادیر z-index در سطح گلوبال مقایسه شوند؟

اما من اینگونه فکر نمی‌کنم، به دلایل زیر:

  • در حال حاضر، افزایش تعداد z-index نوعی اپیدمی است. تصور کنید اگر تک تک عناصر دارای z-index در یک مقیاس قرار بگیرند چقدر بد خواهد بود؟
  • من متخصص مرورگر نیستم، اما حدس می‌زنم که ایجاد زمینه‌ها برای کارایی خوب است و بدون آنها مرورگر مجبور است هر آیتم را با z-index دیگری مقایسه کند که کار خیلی بیشتری به نظر می‌رسد.
  • هنگامی که context را درک کردیم، می‌توانیم از آنها برای مهر و موم کردن عناصر به نفع خود استفاده کنیم. این یک الگوی به خصوص قدرتمند با فریمورک‌های کامپوننت محور مانند React است.

نکته آخر خیلی جالب است. بیایید بیشتر به آن بپردازیم.

مفهوم انتزاع با "isolation"

یکی از خصوصیات CSS مورد علاقه من یکی از مبهم ترین موارد است. من می‌خواهم ویژگی isolation را به شما معرفی کنم، یک گنج پنهان در زبان!

در زیر نحوه استفاده از آن آورده شده است:

.wrapper {
  isolation: isolate;
}

وقتی این خصوصیت را روی عنصری اعمال می‌کنیم، دقیقا یک کار انجام می‌دهد: یک زمینه جدید ایجاد می‌کند.

با وجود بسیاری از روش‌های مختلف برای ایجاد زمینه، چرا ما به این روش نیاز داریم؟ خوب، با هر روش دیگر زمینه‌ها به طور ضمنی ایجاد می‌شوند، در نتیجه برخی تغییرات دیگر به وجود می‌آید. اما isolation یک زمینه به خالص‌ترین شکل ممکن ایجاد می‌کند، به طوری که:

  • نیازی به تخصیص مقدار z-index نیست.
  • در عناصر موجود در موقعیت static قابل استفاده است.
  • به هیچ وجه در رندر فرزند تأثیر نمی‌گذارد.

این ویژگی فوق العاده مفید است، زیرا به ما اجازه می‌دهد تا فرزندان یک عنصر را مهر و موم کنیم.

بیایید به یک مثال نگاه کنیم. من این کامپوننت پاکت نامه را ساخته‌ام. برای مشاهده، نشانگر موس را روی آن نگه دارید:

این شامل چندین لایه است.

من این افکت را در یک کامپوننت ری‌اکت به نام <Envelope> انجام دادم. چیزی شبیه به زیر به نظر می‌رسد (استایل‌های درون خطی برای اختصار استفاده می‌شود):

function Envelope({ children }) {
  return (
    <div>
      <BackPane style={{ zIndex: 1 }} />
      <Letter style={{ zIndex: 3 }}>
        {children}
      </Letter>
      <Shell style={{ zIndex: 4 }} />
      <Flap style={{ zIndex: isOpen ? 2 : 5 }} />
    </div>
  )
}

اگر از خود می‌پرسید که چرا Flap دارای z-index پویا است، به این دلیل است که هنگام باز شدن پاکت باید به پشت حرف تغییر مکان دهد.

یک کامپوننت ری‌اکت خوب مانند یک لباس فضایی در برابر محیط اطراف خود ایزوله می‌شود. اما این لباس فضایی یک نشتی دارد. بررسی کنید که چه اتفاقی می‌افتد وقتی از آن در نزدیکی <header> با z-index: 3 استفاده کنیم:

کامپوننت <Envelope> 4 لایه را در یک div قرار می‌دهد، اما زمینهای ایجاد نمی‌کند. در نتیجه این لایه‌ها می‌توانند با سایر کامپوننت‌ها درهم تنیده شوند.

با استفاده از isolation: isolate در عنصر سطح بالا داخل <Envelope>، تضمین می‌کنیم که به عنوان یک گروه قرار گیرد:

function Envelope({ children }) {
  return (
    <div style={{ isolation: 'isolate' }}>
      <BackPane style={{ zIndex: 1 }} />
      <Letter style={{ zIndex: 3 }}>
        {children}
      </Letter>
      <Shell style={{ zIndex: 4 }} />
      <Flap style={{ zIndex: isOpen ? 2 : 5 }} />
    </div>
  )
}

چرا یک زمینه به روش قدیمی و با position: relative و z-index: 1 ایجاد نمی‌شود؟ خوب، کامپوننت‌های ری‌اکت قابل استفاده مجدد هستند. آیا واقعا 1 در هر شرایطی مقدار z-index مناسب برای این کامپوننت است؟ زیبایی isolation در این است که کامپوننت‌های سازنده ما را انعطاف پذیر نگه می‌دارد.

پشتیبانی مرورگر

خصوصیت isolation چیز جدیدی نیست و از پشتیبانی مرورگر بسیار خوبی برخوردار است و در همه مرورگرها به جز اینترنت اکسپلورر به درستی کار می‌کند.

در صورت نیاز به پشتیبانی Internet Explorer، استفاده از transform: translate (0px) را در نظر می‌گیریم. من هنوز آن را امتحان نکرده‌ام، اما معتقدم که همان نتیجه را خواهد داشت.

دیباگ کردن مشکلات context

متأسفانه موفق به پیدا کردن ابزار زیادی برای کمک به دیباگینگ در این مورد نشدم.

مرورگر Edge دارای یک نمای سه بعدی جالب است که به ما امکان می‌دهد contextها را مشاهده کنیم:

این یک ایده جالب است، اما صادقانه بگویم که کار را بسیار طاقت فرسا می‌کند. یافتن یک عنصر خاص در این نما دشوار است و احساس نمی‌کنم که در پیدا کردن زمینه مورد نظر کمک کننده باشد.

یک ترفند شسته و رفته دیگر نیز وجود دارد که می‌توانید گاهی اوقات از آن استفاده کنید: offsetParent

const element = document.querySelector('.tooltip');
console.log(element.offsetParent); // <main>

این یک راه حل کامل نیست. چون همه stacking contextها از لایه بندی موقعیتی استفاده نمی‌کنند و همه عناصر موجود لزوما یک زمینه جداگانه ایجاد نمی‌کنند.

یک افزونه برای VS Code وجود دارد که هنگام ایجاد زمینه، متن مربوطه را برجسته می‌کند.

همچنین یک افزونه هم برای مرورگر کروم وجود دارد که یک پنل جدید z-index به بخش devtools اضافه می‌کند.

به علاوه یک افزونه دیگر برای مرورگر وجود دارد که مجموعه‌ای از اطلاعات فوق العاده مهم در مورد z-index و contextها را در اختیارمان قرار می‌دهد.

این یک ابزار عالی است و من همیشه از آن استفاده می‌کنم. بنابراین پیشنهاد می‌کنم CSS Stacking Context Inspector را حتما نصب کنید:

اگر شما هم ابزار خاصی برای این کار سراغ دارید، می‌توانید در بخش نظرات به اشتراک بگذارید.

مقایسه context با layer

در devtools مرورگر کروم، یک صفحه "Layers" وجود دارد که لایه‌های عناصر را به صورت جداگانه نشان می‌دهد. آیا این لایه‌ها همان چیدمان زمینه‌ها هستند؟ خیر. ممکن است ظاهرا شبیه به هم باشند اما مفاهیم آنها کاملا متفاوت است.

Stacking contextها یک خصوصیت در مشخصات CSS می‌باشند. آنها بخشی اساسی از چگونگی کارکرد زبان هستند و قرار است مرورگرها آنها را طبق ویژگی‌هایشان پیاده سازی کنند.

از طرف دیگر، Layerها در این مشخصات ذکر نشده‌اند. بلکه نوعی جزئیات پیاده سازی هستند که برخی از مرورگرها مانند کروم از آنها برای بهینه سازی عملکرد استفاده می‌کنند.

به عنوان مثال: با ارتقاء عنصری که در لایه خودش متحرک می‌شود، مرورگر می‌تواند کار را به GPU موکول کند و منجر به انتقال نرم‌تر شود.

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

جمع بندی

contextها مثال خوبی از مکانیزم‌های پنهان CSS هستند. شما می‌توانید سالها بدون ایجاد اطلاع از وجود آنها، رابط‌های CSS بسازید.

تا زمانی که وقت نگذارید و این مکانیزم‌ها را یاد نگیرید، الگوی ذهنی شما همیشه ناقص خواهد ماند.

CSS هشدار یا پیام خطایی ندارد و هنگامی که اتفاق عجیبی رخ دهد، روال مشخصی وجود ندارد که بفهمید مشکل کار از کجاست. این مشکلات ما را از جریان کار خارج کرده و اعتماد به نفس ما را متزلزل می‌کند. من فکر می‌کنم به همین دلیل است که بسیاری از توسعه دهندگان از نوشتن CSS لذت نمی‌برند.

منبع

چه امتیازی به این مقاله می دید؟
خیلی بد
بد
متوسط
خوب
عالی

دیدگاه‌ها و پرسش‌ها

برای ارسال دیدگاه لازم است، ابتدا وارد سایت شوید.

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

آفلاین
user-avatar
عرفان حشمتی @heshmati74
مهندس معماری سیستم های کامپیوتری، طراح و توسعه دهنده وب سایت
دنبال کردن

گفتگو‌ برنامه نویسان

بخشی برای حل مشکلات برنامه‌نویسی و مباحث پیرامون آن وارد شو