بهینه‌سازی انیمیشن با transform و opacity؛ جمع‌بندی عملی یه اصل تکرارشونده

تو این دوره، این اصل که «transform و opacity ارزون‌ترن» رو چند بار از زاویه‌های مختلف دیدیم: تو فصل انیمیشن‌ها به‌صورت مقدمه‌ای، تو فصل قبل به‌صورت فنی و مبتنی بر Layout/Paint/Composite. حالا که تقریباً به پایان فصل پرفورمنس رسیدیم، وقتشه این اصل رو کاملاً عملی کنیم: چطور یه انیمیشن واقعی رو که با ویژگی‌های گرون نوشته شده، بازنویسی کنیم تا فقط از transform و opacity استفاده کنه، حتی وقتی در نگاه اول به نظر نمی‌رسه که همچین کاری ممکنه.

یادآوری خیلی کوتاه

از فصل قبل یادمونه: transform و opacity مستقیم می‌رن سراغ مرحله‌ی Composite (روی GPU)، بدون این‌که Layout یا Paint رو فعال کنن. این یعنی این دو ویژگی، تقریباً همیشه انتخاب اول برای هر انیمیشنی هستن که قراره روون اجرا بشه.

مورد اول: جابه‌جایی با left/top به‌جای translate

/* گرون: هر فریم Layout رو دوباره محاسبه می‌کنه */
.toast {
  position: fixed;
  left: -300px;
  transition: left 0.3s ease;
}
.toast.show {
  left: 20px;
}

/* ارزون: فقط Composite */
.toast {
  position: fixed;
  left: 20px;
  transform: translateX(-320px);
  transition: transform 0.3s ease;
}
.toast.show {
  transform: translateX(0);
}

نکته‌ی این تبدیل اینه که موقعیت پایه (left: 20px) رو ثابت نگه می‌داریم و کل حرکت رو با translateX انجام می‌دیم؛ یعنی از نظر مرورگر، المنت همیشه سرجای نهایی‌شه، فقط با یه افست بصری جابه‌جا شده که هزینه‌ای برای Layout نداره.

مورد دوم: بزرگ/کوچیک شدن با width/height به‌جای scale

/* گرون: تغییر width/height باعث Layout می‌شه */
.modal {
  width: 0;
  height: 0;
  transition: width 0.25s ease, height 0.25s ease;
}
.modal.open {
  width: 400px;
  height: 300px;
}

/* ارزون: فقط Composite */
.modal {
  width: 400px;
  height: 300px;
  transform: scale(0);
  transition: transform 0.25s ease;
}
.modal.open {
  transform: scale(1);
}

اینجا هم همون الگو تکرار می‌شه: سایز نهایی واقعی رو از همون اول روی المنت می‌ذاریم، و فقط با scale بصری کوچیکش می‌کنیم. یه نکته‌ی مهم: با این روش، transform-origin (که تو فصل ۱۹ دیدیم) تعیین می‌کنه بزرگ‌شدن از کدوم نقطه شروع بشه؛ برای یه مودال که وسط صفحه باز می‌شه، معمولاً center (پیش‌فرض) همون چیزیه که می‌خواید.

مورد سوم: محو شدن با visibility/height به‌جای opacity برای collapse کردن

یه حالت پیچیده‌تر: جمع کردن یه آکاردئون یا پنل. اینجا فقط opacity کافی نیست، چون المنت باید فضاش رو هم از دست بده:

/* گرون: انیمیت کردن height مستقیم */
.panel {
  height: 200px;
  overflow: hidden;
  transition: height 0.3s ease;
}
.panel.collapsed {
  height: 0;
}

این یه مورد جالبه چون واقعاً نمی‌شه جای height رو با transform عوض کرد؛ چون scaleY باعث می‌شه فضای اشغالی المنت تو چیدمان تغییر نکنه (فقط ظاهرش فشرده می‌شه)، در حالی که برای یه آکاردئون واقعاً می‌خوایم فضا هم آزاد بشه. تو این موارد خاص، دو راه‌حل معقول وجود داره: یا قبول کنید که Layout لازمه (چون خود ماهیت افکت اینه)، یا از یه تکنیک جایگزین مثل grid-template-rows: 0fr به 1fr استفاده کنید که تو مرورگرهای مدرن معمولاً روون‌تر از انیمیت مستقیم height عمل می‌کنه:

.panel-wrapper {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 0.3s ease;
}
.panel-wrapper.collapsed {
  grid-template-rows: 0fr;
}
.panel-content {
  overflow: hidden;
}

این یه یادآوریه که «همیشه transform/opacity» یه قانون مطلق نیست؛ گاهی خود منطق افکت ایجاب می‌کنه از یه ویژگی گرون‌تر استفاده کنید، و مهم اینه که این تصمیم رو آگاهانه بگیرید، نه از سر بی‌اطلاعی.

مورد چهارم: تغییر رنگ پس‌زمینه در یه لیست بزرگ

برای یه المنت تنها، انیمیت کردن background-color (که فقط Paint رو فعال می‌کنه، نه Layout) معمولاً کاملاً بی‌مشکله. ولی وقتی این تغییر رو صدها آیتم یه لیست هم‌زمان اعمال بشه (مثلاً یه افکت highlight دسته‌جمعی)، حجم Paint می‌تونه محسوس بشه. یه ترفند رایج، استفاده از یه لایه‌ی ::after نیمه‌شفاف با opacity به‌جای تغییر مستقیم background-color:

.item {
  position: relative;
}
.item::after {
  content: "";
  position: absolute;
  inset: 0;
  background: yellow;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}
.item.highlight::after {
  opacity: 1;
}

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

چک‌لیست عملی قبل از نوشتن یه انیمیشن

سؤالاگه جوابش «بله» باشه
آیا فقط جابه‌جایی، بزرگ/کوچیک شدن، چرخش یا محو شدنه؟از translate، scale، rotate یا opacity استفاده کنید
آیا فضای اشغالی المنت تو چیدمان هم باید تغییر کنه؟Layout اجتناب‌ناپذیره؛ سعی کنید تعداد المنت‌های درگیر رو کم نگه دارید
آیا این افکت رو تعداد زیادی المنت هم‌زمان اجرا می‌شه؟حتی Composite رو هم تست کنید؛ ممکنه لازم باشه تعداد رو محدود کنید
نکته مهم: این تکنیک‌ها رو کورکورانه همه‌جا اعمال نکنید. برای یه دکمه‌ی تکی که به‌ندرت انیمیت می‌شه، فرقی بین left و transform اصلاً حس نمی‌شه. این بهینه‌سازی‌ها وقتی واقعاً اهمیت پیدا می‌کنن که تعداد المنت‌ها زیاده، انیمیشن مکرره (مثل اسکرول یا لودر بی‌نهایت)، یا هدفتون دستگاه‌های ضعیف‌تره.

اشتباه رایج: بازنویسی همه‌چیز بدون اندازه‌گیری قبل و بعد

یه اشتباه رایج اینه که یه تیم تصمیم می‌گیره کل انیمیشن‌های پروژه رو به transform/opacity تبدیل کنه، بدون این‌که اول با تب Performance (که تو درس قبل دیدیم) چک کنه آیا واقعاً مشکلی وجود داشته یا نه. این کار می‌تونه وقت زیادی رو صرف چیزی کنه که از همون اول هم محسوس نبوده. رویکرد درست همیشه اینه: اول اندازه‌گیری کنید، ببینید کجای pipeline واقعاً گیر داره، بعد فقط همون بخش رو بهینه کنید.

جمع‌بندی

اصل «transform و opacity ارزون‌ترن» یه قانون کلیه که با تکنیک‌های عملی مثل جایگزینی left با translate، width/height با scale، و لایه‌های جدا برای تغییرات رنگی، قابل پیاده‌سازیه. ولی همه‌ی افکت‌ها (مثل collapse کردن واقعی فضا) با این روش قابل جایگزینی نیستن، و مهم‌ترین قدم قبل از هر بهینه‌سازی، اندازه‌گیریه، نه حدس زدن.