Layout، Paint، Composite؛ سه مرحله‌ای که مرورگر برای رندر هر فریم طی می‌کنه

تو فصل انیمیشن‌ها (فصل ۲۰) به‌طور مختصر با این سه مرحله آشنا شدیم تا بفهمیم چرا transform و opacity از width یا left ارزون‌ترن. حالا که این فصل کاملاً به پرفورمنس اختصاص داره، وقتشه با جزئیات بیشتری ببینیم مرورگر دقیقاً پشت‌صحنه چیکار می‌کنه، چون فهم این مراحل بهتون کمک می‌کنه هر تصمیم CSS ای رو از نظر هزینه‌ی رندر ارزیابی کنید، نه فقط چند مورد خاص که از قبل حفظ کردید.

مرورگر یه صفحه رو چطور به پیکسل تبدیل می‌کنه؟

وقتی یه صفحه لود می‌شه یا چیزی توش تغییر می‌کنه، مرورگر یه خط لوله (pipeline) چندمرحله‌ای رو طی می‌کنه تا نتیجه رو رو صفحه‌نمایش نشون بده. این مراحل همیشه به همین ترتیب اتفاق می‌افتن؛ نمی‌شه یکی رو بدون دیگری اجرا کرد:

مرحلهکاری که انجام می‌شه
Styleمشخص کردن این‌که کدوم قانون CSS به کدوم المنت اعمال می‌شه
Layoutمحاسبه‌ی دقیق موقعیت و سایز هر المنت رو صفحه
Paintرنگ‌آمیزی پیکسل‌ها؛ یعنی تعیین شکل ظاهری هر المنت (رنگ، سایه، متن)
Compositeچیدن لایه‌های مختلف رو هم و نمایش نهایی رو صفحه

نکته‌ی کلیدی اینه: هر تغییری که تو یه المنت بدید، لازم نیست همیشه از مرحله‌ی Layout شروع بشه. بسته به این‌که چه ویژگی‌ای رو تغییر دادید، مرورگر می‌تونه از یکی از این مراحل میانی شروع کنه، نه از اول.

Layout: گرون‌ترین مرحله، چون همه‌چیز به هم وابسته‌ست

Layout (که بهش reflow هم می‌گن) یعنی مرورگر باید موقعیت و سایز دقیق هر المنت رو، با توجه به المنت‌های اطرافش، دوباره محاسبه کنه. مشکل اینجاست: چیدمان وب ذاتاً به‌هم‌وابسته‌ست؛ تغییر عرض یه المنت می‌تونه باعث بشه المنت بعدی‌اش جابه‌جا بشه، که اون هم باعث جابه‌جایی بعدی بشه، و این تا آخر صفحه ممکنه ادامه پیدا کنه:

/* تغییر این مقدار، Layout کل صفحه رو دوباره محاسبه می‌کنه */
.sidebar {
  width: 320px; /* قبلاً 280px بود */
}

ویژگی‌هایی که باعث Layout می‌شن شامل width، height، padding، margin، top/left/right/bottom (وقتی position غیر از static باشه)، font-size و تغییر خود محتوای متنی هستن.

Paint: رنگ‌آمیزی بدون تغییر موقعیت

Paint یه مرحله سبک‌تره؛ اینجا مرورگر موقعیت و سایز المنت‌ها رو از قبل می‌دونه (چون Layout قبلاً تموم شده)، فقط باید بفهمه هر پیکسل چه رنگی باید بشه. تغییر background-color، box-shadow، color یا border-radius باعث Layout نمی‌شه (چون سایز و موقعیت المنت عوض نشده)، ولی همچنان باید Paint دوباره اجرا بشه:

.card:hover {
  background-color: #f0f0f0; /* فقط Paint، نه Layout */
}

Composite: ارزون‌ترین مرحله، چون فقط لایه‌ها رو جابه‌جا می‌کنه

بعضی ویژگی‌ها، به‌جای این‌که مرورگر رو مجبور کنن دوباره محاسبه یا رنگ‌آمیزی کنه، فقط باعث می‌شن یه لایه‌ی از قبل رندرشده، جابه‌جا یا تغییر شفافیت بده؛ دقیقاً مثل جابه‌جا کردن یه عکس روی یه اسلاید پاورپوینت، نه دوباره کشیدنش. transform و opacity دقیقاً همین کارو می‌کنن:

.card:hover {
  transform: translateY(-4px); /* فقط Composite */
  opacity: 0.9; /* فقط Composite */
}

این مرحله معمولاً رو GPU انجام می‌شه (نه CPU)، که خودش دلیل دیگه‌ای برای سریع‌تر بودنشه؛ GPU ها دقیقاً برای همین نوع کار (جابه‌جایی و ترکیب لایه‌های تصویری) بهینه شدن.

چرا یه تغییر ساده گاهی باعث یه زنجیره‌ی گرون می‌شه؟

یه اشتباه رایج اینه که فکر کنیم فقط خود المنتی که تغییر می‌کنه هزینه داره؛ در واقع، Layout می‌تونه به بالا (والدها) و پایین (فرزندها) هم سرایت کنه. مثلاً اگه یه فرزند با height داینامیک عوض بشه، ممکنه ارتفاع والدش هم (اگه به محتوا وابسته باشه) عوض بشه، که اون هم رو گرندپرنتش اثر بذاره. به همین دلیله که انیمیت کردن height رو یه لیست طولانی، معمولاً محسوس‌تر کند از انیمیت کردن transform رو همون تعداد المنته.

Layout Thrashing: وقتی جاوااسکریپت باعث Layout اجباری و مکرر می‌شه

یه مشکل مرتبط (بیشتر جاوااسکریپتی تا خالص CSS) اینه که وقتی کدی پشت سر هم بین «نوشتن یه استایل» و «خوندن یه مقدار محاسبه‌شده مثل offsetHeight» رفت‌وآمد می‌کنه، مرورگر مجبور می‌شه هر بار Layout رو فوری و مکرر اجرا کنه، به‌جای این‌که صبر کنه و یه‌جا انجامش بده. این پدیده «Layout Thrashing» نام داره. به‌عنوان یه نویسنده‌ی CSS، دونستن این مفهوم کمک می‌کنه بفهمید چرا بعضی جاوااسکریپت‌های به‌ظاهر بی‌ربط، می‌تونن پرفورمنس CSS شما رو هم خراب کنن.

نکته مهم: این سلسله‌مراتب (Layout گرون‌تر از Paint، Paint گرون‌تر از Composite) یه قانون کلیه، نه یه عدد ثابت. هزینه‌ی واقعی به تعداد المنت‌های صفحه، پیچیدگی چیدمان، و قدرت دستگاه کاربر بستگی داره. برای یه صفحه‌ی ساده با چند المنت، حتی Layout هم ممکنه اصلاً محسوس نباشه؛ ولی برای صفحاتی با هزاران المنت یا چیدمان‌های پیچیده، این تفاوت‌ها به‌شدت به چشم میان.

چطور تو DevTools این مراحل رو ببینیم؟

تب Performance تو Chrome DevTools، یه نمای زمانی از این مراحل بهتون می‌ده؛ می‌تونید یه تعامل (مثل هاور یا اسکرول) رو ضبط کنید و ببینید مرورگر چقدر زمان صرف هر کدوم از Layout (که اونجا Recalculate Style / Layout نام داره)، Paint و Composite کرده. این دقیق‌ترین راه برای فهمیدن اینه که یه انیمیشن یا تعامل خاص واقعاً کجای این pipeline گیر کرده، به‌جای حدس زدن.

اشتباه رایج: تعمیم دادن «transform و opacity همیشه ارزونن» به همه‌جا

یه سوءبرداشت رایج اینه که چون transform و opacity فقط Composite رو فعال می‌کنن، پس همیشه و برای هر تعداد المنت بی‌هزینه‌ن. این درست نیست؛ اگه صدها یا هزاران المنت هم‌زمان transform بگیرن (مثلاً یه انیمیشن ذرات پیچیده)، حتی Composite هم می‌تونه رو دستگاه‌های ضعیف‌تر محسوس بشه. اصل کلی («ارزون‌تر از Layout») همچنان درسته، ولی «ارزون» به معنی «رایگان» نیست.

جمع‌بندی

مرورگر برای رندر هر فریم، از Layout (محاسبه‌ی موقعیت و سایز، گرون‌ترین مرحله چون به‌هم‌وابسته‌ست) به Paint (رنگ‌آمیزی پیکسل‌ها) و در نهایت Composite (چیدن لایه‌ها، معمولاً رو GPU) می‌ره. هر ویژگی CSS ای که تغییر بدید، مشخص می‌کنه از کدوم مرحله باید دوباره شروع بشه؛ هرچی از Layout دورتر باشید (یعنی نزدیک‌تر به Composite)، معمولاً پرفورمنس بهتریه. تب Performance تو DevTools دقیق‌ترین ابزار برای دیدن این مراحل تو عمله، نه فقط قانون کلی «transform همیشه سریعه».