Layers و Stacking Context چی هستند و چجوری می‌تونن Performance سایت‌مون رو بهتر کنند؟

Layers و Stacking Context چی هستند و چجوری می‌تونن Performance سایت‌مون رو بهتر کنند؟

دی ۱۳, ۱۴۰۴

مقدمه

دو مفهوم Stacking context و Layers در فرآیند تبدیل کدهای ما به صفحات وب در مرورگر معنا پیدا می‌کنند. این دو مفهوم خیلی شبیه هم هستند و حتی بعضی جاها اشتباها به جای هم به کار میرن اما تفاوت دارند. دونستن این تفاوت بهمون کمک می‌کنه که CSS ای بنویسیم که به سایت با کیفیت‌تری منتج بشه!

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

فرآیند رندرینگ مرورگر

وقتی یه صفحه وب رو باز می‌کنید، مرورگر چند مرحله اصلی رو طی می‌کنه تا کدهای شما رو به یه صفحه قابل مشاهده تبدیل کنه:

Normal flow visualization

۱. ساخت DOM (Document Object Model)

وقتی مرورگر کدهای HTML شما رو می‌خونه، یه ساختار درختی به نام DOM می‌سازه. این درخت شامل تمام المان‌های HTML صفحه شما می‌شه، از <html> تا آخرین <div> یا <span>. هر المان یه node در این درخت محسوب می‌شه.

۲. ساخت CSSDOM (CSS Object Model)

همزمان با ساخت DOM، مرورگر کدهای CSS رو هم پردازش می‌کنه و یه ساختار درختی دیگه به نام CSSDOM می‌سازه. این درخت شامل تمام استایل‌ها و قوانین CSS می‌شه که مشخص می‌کنه هر المان چجوری باید نمایش داده بشه.

۳. ترکیب DOM و CSSDOM - درخت رندر (Render Tree)

بعد از ساخت DOM و CSSDOM، مرورگر این دو درخت رو با هم ترکیب می‌کنه و یه درخت رندر ایجاد می‌کنه. این درخت فقط شامل المان‌هایی می‌شه که قابل نمایش هستند (مثلا المان‌های با display: none در این درخت وجود ندارند).

۴. محاسبه Layout (Reflow)

در این مرحله، مرورگر موقعیت و اندازه دقیق هر المان رو در صفحه محاسبه می‌کنه. این فرآیند به عنوان reflow یا layout شناخته می‌شه. مرورگر از اطلاعات درخت رندر استفاده می‌کنه تا بفهمه هر المان کجا باید قرار بگیره و چقدر فضا اشغال کنه.

۵. Paint (رنگ‌آمیزی)

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

۶. Composition (ترکیب لایه‌ها)

در مرحله آخر، مرورگر لایه‌های مختلف رو با هم ترکیب می‌کنه تا صفحه نهایی رو نمایش بده.

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

نکات مهم

  1. از بین مراحل فوق، همگی به جز Compostion کاملا در CPU انجام می ‌شوند.
  2. امور مربوط به ایجاد Stacking Context در بخش Paint رخ می‌دهد. (جلوتر می‌رسیم)
  3. امور مربوط به Layers (جلوتر می‌رسیم که چی هستن) در بخش Composition رخ می دهد. (عمدتا در GPU)
  4. زمانبر ترین بخش این فرآیندها، بخش Layout یا Reflow است، چون باید x,y یک المان یا مختصاتش رو CPU دربیاره و برای این فرآیند باید تمام المان‌های توی صفحه رو درنظر بگیره.

ذکر این نکته هم لازمه که، چنانچه سیستمی GPU نداشته باشه (که دیگه اینروزا خیلی بعیده) قضیه hand-off میشه به همون CPU مون!

بنابراین اگر حرکاتی(!) بزنیم که Reflow رخ نده، می‌تونیم انتظار سرعت و عملکرد بهتری در وبسایتمون داشته باشیم. منظورمون از حرکات هم صرفا تغییر css مونه.

البته Reflow لازم و ضروریه اما یه وقت‌هایی واسه یه چیز بیخودی و دائما در حال انجام (مثل انیمیشن‌ها) ما اشتباها موجبات Reflow رو فراهم می‌کنیم (در حالیکه می‌تونیم نکنیم) و اینجوری سایتمون کند میشه و بعضا هنگ می‌کنه.

چه حرکاتی نیازی به reflow ندارند؟

اگر بخوایم براش یه لیست درست کنیم، قطعا به خاطر نمیشه سپرد و اصلا کار جالبی هم نیست!

فقط کافیه این rule of thumb رو بخاطر بسپارید:

هر تغییر CSS ای که ابعاد و موقعیت x,y یک المان و همسایه‌هاش رو عوض نکنه، موجب reflow نمیشه.

خب دیگه خودتون می تونید یه لیست درست کنید، کارهایی مثل تغییر color و background و opacity تنها نمونه‌هایی از این لیست هستند.

مثال عملی: انیمیشن با Reflow و بدون Reflow

یه انیمیشن ساده رو با دو روش زیر انجام بدیم یکی با مارجین یکی با ترنسفورم. اولی یه nightmare برای مرورگره و دومی یه چیزه خیلی سریع و مناسب.

روش اول: استفاده از margin (دردسرساز!)

<div class="container">
  <div class="box bad-animation">Animation with margin</div>
</div>
.box {
  width: 100px;
  height: 100px;
  background: blue;
  margin-top: 10px;
}

.bad-animation:hover {
  margin-top: 110px; /*  This cause reflow!  */
  transition: margin-top 0.3s ease;
}

روش دوم: استفاده از transform (بهینه!)

<div class="container">
  <div class="box good-animation">Animation with transform</div>
</div>
.box {
  width: 100px;
  height: 100px;
  background: green;
  margin-top: 10px;
}

.good-animation:hover {
  transform: translateY(100px); /* This isn't cause reflow! */
  transition: transform 0.3s ease;
}

توضیح:

  • در روش اول، با تغییر margin-top، مرورگر مجبور هست تمام المان‌های بعدی رو مجددا محاسبه کنه (reflow)
  • در روش دوم، transform فقط المان رو در Stacking context جداگانه‌ای حرکت میده و نیازی به محاسبه مجدد موقعیت سایر المان‌ها نداره

بریم سراغ Stacking Context

مقدمه: حالت چینش normal flow چیه؟

فرض کنید یه فایل HTML مانند فایل زیر دارید. تمامی المنت‌ها در این فایل، در normal flow زیست می کنند.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple HTML Page</title>
</head>
<body>
    <h1>Hello, World!</h1>
    <p>This is a simple HTML page with no styles or JavaScript.</p>
</body>
</html>

مفهوم normal flow در مرورگر: یعنی المان های از نوع block در زیر هم به صورت عمودی قرار می گیرند و المان های از نوع inline به صورت افقی در کنار هم. تصویر زیر:

Normal flow visualization

تمام المنت‌هایی که در HTML داریم مثل div, p, a, article, و غیره همگی به طور پیشفرض براساس normal flow توسط مرورگر چیده می‌شوند، مگر اینکه با Stacking Context آن‌ها رو از normal flow خارج کنیم!

تعریف ‌Stacking Context

اگر با واژه stack آشنا نیستید باید بگم، stack مشابه طبقات یک کیک است که روی هم قرار می‌گیرند و وقتی می‌خواهیم کیک بخوریم قاعدتا اون طبقه بالایی رو اول برمی‌داریم. به عبارت دیگر آخرین (روترین) لایه روی کیک، اولین لایه‌ای است که باید برداریم. (لطفا تصورش کنید!)

حالا Stacking Context در مرورگر طبق تعریف سایت MDN می‌شود:

اگر ساختار چینش المان ها در صفحه HTML را در یک صفحه سه بعدی در نظر بگیریم، به محور Z و نحوه قرارگیری المان ها در آن stacking context می گویند.

چجوری stacking context ایجاد کنیم؟

هر کدام از element های زیر می‌توانند منجر به ایجاد stacking context جدید شوند:

  • المنت (<html>)
  • المنتی با مقدار position برابر absolute یا relative و مقدار z-index غیر از auto
  • المنتی با مقدار position برابر fixed یا sticky
  • المنتی با مقدار container-type برابر size یا inline-size
  • المنتی که یک flex item است با مقدار z-index غیر از auto
  • المنتی که یک grid item است با مقدار z-index غیر از auto
  • المنتی با مقدار opacity کمتر از 1
  • المنتی با مقدار mix-blend-mode غیر از normal
  • المنتی با هر یک از خواص زیر با مقداری غیر از none:
    • transform (مثالی که بالاتر زده بودیم مثلا همین رو فقط داشت)
    • scale
    • rotate
    • translate
    • filter
    • backdrop-filter
    • perspective
    • clip-path
    • mask / mask-image / mask-border

برای مشاهده لیست کاملشون به این لینک رجوع کنید.

منظور از المان همان تگ‌های HTML است

رابطه z-index و stacking context

نکته مهمی که باید بدونید اینه که z-index فقط داخل یه stacking context معناداره. یعنی اگر دو تا المان در stacking context های مختلف باشند، z-index بالاتری که در یه context پایین‌تر باشه، نمی‌تونه روی المانی با z-index پایین‌تر در یه context بالاتری تاثیر بذاره. مثال:

<div class="header" style="position: relative; z-index: 2;">
Site Header
</div>
<div class="main" style="position: relative; z-index: 1;">
  <div class="tooltip" style="position: absolute; z-index: 999999;">
   Content of tooltip
  </div>
  [main content of site]
</div>

در این مثال، حتی با اینکه tooltip دارای z-index: 999999 هست، باز هم زیر هدر قرار می‌گیره چون هدر یه stacking context جداگانه ایجاد کرده و tooltip داخل stacking context اصلی قرار داره.

این یه مثال کوچیک بود و قصد من هم صحبت راجع به z-index نیست، اگر مسئله ارتباط بین z-index و Stacking Context براتون مبهمه، به این لینک در وبسایت MDN رجوع کنید که یه مثال خیلی جذاب برای توضیح همین مفهوم داره.

آیا stacking context می‌تونه روی Performance نتیجه بهتری بگذاره؟

خیر! اصلا این نحوه تفکر راجع به Stacking Context اشتباه است. اساسا stack context یه ویژگی CSS مشابه سایر ویژگی های آن مثل padding,margin,font-size و ... است. آیا با حرکتی روی padding می‌تونیم ‌Performance بهتری بگیریم؟

‌ قطعا خیر!

آیا stacking context می‌تونه روی Performance نتیجه بدی بگذاره؟

قطعا بله!

همانطور که با حرکتی بدی روی padding میشه Performance بدتری گرفت. (سعی کنید padding:2e6 بگذارید برای یک المنت‌تون!) همین موضوع برای Stacking Context هم صادق است یعنی با استفاده زیاد از Stacking Context علاوه بر اینکه ممکنه Performance بدتری بگیرید بلکه ممکنه بین z-index ها گم بشید و دیباگ کد عذاب آور بشه براتون!

بنابراین حواستون باشه چیا Stacking Context درست می‌کنند و در حد لزوم ازش استفاده کنید و بیخودی برای خودتون دردسر درست نکنید. پس این قاعده رو از اینجا به خاطر بسپارید:

مفهوم Stacking Context به خودی خود هیچ ارتباطی با افزایش Performance و جلوگیری از reflow نداره.

بریم سراغ Layers

منظور از Layers در مرورگر چیه؟ خب برای اینکه اینو بفهمیم باید یه نگاه دقیقی به محل این مرحله بندازیم. یعنی لایه Composition.

تعریف Layers در مرورگر

Layers در واقع لایه‌های مجزایی هستن که مرورگر برای بهینه‌سازی فرآیند رندرینگ ایجاد می‌کنه. هر لایه می‌تونه شامل یک یا چند المان باشه و به GPU اجازه میده اونا رو مستقل از هم پردازش کنه.

نکات کلیدی درباره Layers:

  • Layers عمدتا در بخش Composition مرورگر پردازش میشن
  • پردازش Layers عمدتا توسط GPU انجام میشه (نه CPU)
  • هدف اصلی Layers افزایش عملکرد و کاهش بار پردازشی CPU
  • هر لایه می‌تونه به صورت مستقل انیمیشن بده و آپدیت بشه

چه زمانی مرورگر Layers جدید ایجاد می‌کنه؟

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

ویژگی‌هایی که لایه ایجاد می‌کنن:

  • transform
  • opacity
  • filter
  • backdrop-filter
  • position: fixed یا sticky
  • will-change
  • contain: paint یا contain: layout

المان‌های بزرگ:

  • المان‌های با محتوای زیاد
  • المان‌های با تصاویر بزرگ
  • المان‌های با گرادینت‌های پیچیده

ارتباط بین Layers و Performance

مزایای Layers:

  • GPU Acceleration: انیمیشن‌های لایه‌ها با کمک GPU اجرا میشن و خیلی سریع‌ترن
  • Partial Updates: وقتی فقط یه بخش از صفحه تغییر می‌کنه، فقط لایه‌های مربوطه آپدیت میشن
  • Reduced Paint: اگر المان داخل یه لایه باشه، تغییرش فقط اون لایه رو repaint میکنه نه کل صفحه

معایب Layers (اگر زیاد ایجاد بشن):

  • Memory Usage: هر لایه حافظه اشغال می‌کنه
  • Compositing Overhead: ترکیب لایه‌ها می‌تونه performance رو کاهش بده
  • Texture Upload: ایجاد لایه‌های جدید نیاز به آپلود بافت به GPU داره که می‌تونه کند باشه

تفاوت اصلی بین Stacking Context و Layers

ویژگی Stacking Context Layers
محل ایجاد بخش Paint مرورگر بخش Composition مرورگر
هدف کنترل لایه‌بندی Z-index بهینه‌سازی رندرینگ
تأثیر بر performance معمولا منفی (اگر زیاد استفاده بشه) معمولا مثبت (اگر بهینه استفاده بشه)
کنترل توسط توسعه‌دهنده مستقیم (با CSS) غیرمستقیم (مرورگر تصمیم می‌گیره)
ارتباط با GPU معمولا ندارد مستقیم (GPU acceleration)

عملی: چطور از این مفاهیم برای بهبود Performance استفاده کنیم؟

حالا که با مفاهیم Stacking Context و Layers آشنا شدیم، ببینیم چطور می‌تونیم ازشون برای بهبود performance استفاده کنیم:

1. انیمیشن‌ها رو بهینه‌تر بنویسیم

بد: استفاده از properties که باعث reflow میشن

.element {
  transition: left 0.3s ease; /* left باعث reflow میشه! */
  position: absolute;
  left: 0;
}

.element:hover {
  left: 100px;
}

خوب: استفاده از transform

.element {
  transition: transform 0.3s ease; /* transform باعث reflow نمیشه! */
  position: absolute;
  left: 0;
}

.element:hover {
  transform: translateX(100px);
}

2. از will-change به شکل هوشمندانه استفاده کنیم

بد: استفاده بی‌جا

.element {
  will-change: transform; /* برای همه المان‌ها! */
}

خوب: استفاده هدفمند

.element {
  /* فقط برای المان‌هایی که واقعا انیمیشن دارن */
}

.element:hover {
  will-change: transform;
}

3. لایه‌های غیرضروری رو حذف کنیم

اگر المان‌های زیادی دارن که stacking context ایجاد می‌کنن ولی واقعا نیاز ندارن، سعی کنید اونا رو ساده‌سازی کنید.

4. استفاده از contain برای بهینه‌سازی

.element {
  contain: layout paint; /* به مرورگر اجازه میده فقط این المان رو در نظر بگیره */
}

مثال کامل: مقایسه عملکرد

بیایید یه مثال کامل ببینیم که تفاوت بین رویکردهای مختلف رو نشون بده:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Performance Comparison</title>
    <style>
        .container {
            width: 800px;
            height: 600px;
            border: 2px solid #333;
            position: relative;
            margin: 20px;
        }
        
        /* Bad Example - Reflow Animation */
        .bad-box {
            width: 50px;
            height: 50px;
            background: red;
            position: absolute;
            top: 10px;
            left: 10px;
            transition: all 0.3s ease;
        }
        
        .bad-box:hover {
            left: 300px;
            top: 200px;
        }
        
        /* Good Example - Transform Animation */
        .good-box {
            width: 50px;
            height: 50px;
            background: green;
            position: absolute;
            top: 80px;
            left: 10px;
            transition: transform 0.3s ease;
        }
        
        .good-box:hover {
            transform: translate(290px, 110px);
        }
        
        /* Layer Example - GPU Accelerated */
        .layer-box {
            width: 50px;
            height: 50px;
            background: blue;
            position: absolute;
            top: 150px;
            left: 10px;
            will-change: transform;
            transition: transform 0.3s ease;
        }
        
        .layer-box:hover {
            transform: translate(290px, 110px);
        }
        
        /* Static Box for Reference */
        .static-box {
            width: 50px;
            height: 50px;
            background: gray;
            position: absolute;
            top: 220px;
            left: 10px;
        }
        
        .info {
            margin-top: 300px;
            padding: 10px;
            background: #f0f0f0;
        }
    </style>
</head>
<body>
    <h1>مقایسه عملکرد انیمیشن‌ها</h1>
    
    <div class="container">
        <div class="bad-box"></div>
        <div class="good-box"></div>
        <div class="layer-box"></div>
        <div class="static-box"></div>
        
        <div class="info">
            <p>• جعبه قرمز: انیمیشن با reflow (کند)</p>
            <p>• جعبه سبز: انیمیشن با transform (سریع)</p>
            <p>• جعقه آبی: انیمیشن با transform + will-change (سرعت GPU)</p>
            <p>• جعقه خاکستری: ثابت (مرجع)</p>
        </div>
    </div>
    
    <p>ماوس رو روی جعبه‌ها ببرید تا انیمیشن‌ها رو ببینید</p>
</body>
</html>

در این مثال:

  • جعبه قرمز با تغییر left و top باعث reflow میشه و کندترین روشه
  • جعبه سبز با transform کار میکنه و سریعتره
  • جعقه آبی علاوه بر transform از will-change هم استفاده میکنه که مرورگر رو آماده میکنه تا اون رو در GPU پردازش کنه

ابزارها و تکنیک‌های دیباگ stacking context و Layers

اگر تا حالا با مشکلات لایه‌بندی در CSS دست و پنجه نرم کردید، می‌دونید که دیباگ کردن stacking context می‌تونه چقدر چالش‌برانگیز باشه. خب، خبر خوب اینه که چند تا ابزار عالی وجود داره که می‌تونن بهتون کمک کنن!

۱. تب Layers در Chrome DevTools

  1. Chrome DevTools رو باز کنید (F12)
  2. به تب Performance برید
  3. یه عملیات صفحه رو ضبط کنید
  4. در بخش Layers، لیست تمام لایه‌های صفحه رو می‌تونید ببینید
  5. با کلیک روی هر لایه، اطلاعات دقیقش رو می‌تونید ببینید

نکته: مرورگر Edge یه ابزار جالب به نام 3D View داره که بهتون اجازه میده stacking context ها رو به صورت سه بعدی ببینید. این ابزار می‌تونه برای درک ساختار لایه‌بندی صفحات پیچیده خیلی مفید باشه.

۲. اکستنشن VSCode برای stacking context

Mike Rheault یه اکستنشن VSCode ایجاد کرده که مشکلات احتمالی stacking context رو در کدهای CSS و SCSS بهتون نشون میده.

۳. استفاده از DevTools برای تحلیل Performance

  1. به تب Performance برید
  2. Record رو بزنید
  3. صفحه رو آپدیت یا انیمیشن رو اجرا کنید
  4. تیک GPU Processing رو بزنید
  5. ببینید کدوم بخش‌ها در GPU پردازش شدن

4. تست با forced compositing

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

.test-element {
  /* این استایل‌ها معمولا باعث ایجاد لایه میشن */
  transform: translateZ(0);
  backface-visibility: hidden;
  perspective: 1000;
}

اگه بعد از اضافه کردن این استایل‌ها انیمیشن المان روان‌تر شد، یعنی قبلا لایه نداشته و حالا داره از GPU استفاده می‌کنه.

نتیجه‌گیری و جمع‌بندی

در این مقاله با دو مفهوم مهم در CSS یعنی Stacking Context و Layers آشنا شدیم. نکات کلیدی که یاد گرفتیم:

  1. Stacking Context:

    • برای کنترل لایه‌بندی Z-index استفاده میشه
    • ایجادش معمولا performance منفی داره
    • باید با احتیاط و فقط در موارد ضروری استفاده بشه
  2. Layers:

    • برای بهینه‌سازی رندرینگ و GPU acceleration استفاده میشن
    • معمولا performance مثبت دارن
    • با استفاده از properties مثل transform و will-change می‌تونیم لایه‌های بهینه ایجاد کنیم
  3. Performance Tips:

    • همیشه برای انیمیشن‌ها از transform استفاده کنید نه properties که باعث reflow میشن
    • از will-change به شکل هدفمند استفاده کنید
    • با ابزارهای DevTools لایه‌ها و performance رو تحلیل کنید
    • به جای ایجاد لایه‌های زیاد، سعی کنید از contain برای بهینه‌سازی استفاده کنید

یادگیری این مفاهیم به شما کمک می‌کنه CSS بهتری بنویسید که هم از نظر بصری جذاب باشه و هم performance خوبی داشته باشه. به یاد داشته باشید که هدف نهایی اینه که کاربران تجربه روان و سریعی از سایت شما داشته باشن.

موفق باشید!

مطالعه بیشتر