کنترل دقیق منحنی سرعت: cubic-bezier() و steps()

در درس ۱۸-۱، به‌طور مقدماتی با transition-timing-function آشنا شدیم و گفتیم جزئیاتش رو به این درس موکول می‌کنیم. حالا وقتشه ببینیم دقیقاً چطور می‌شه منحنی سرعت یک انتقال رو، هم با کلیدواژه‌های آماده و هم با کنترل کاملاً دستی، تعریف کرد.

مرور: کلیدواژه‌های آماده

.a { transition-timing-function: linear; }
.b { transition-timing-function: ease; }
.c { transition-timing-function: ease-in; }
.d { transition-timing-function: ease-out; }
.e { transition-timing-function: ease-in-out; }
مقدار رفتار
linear سرعت کاملاً ثابت، از ابتدا تا انتها
ease (پیش‌فرض) شروع نسبتاً سریع، پایان کند
ease-in شروع کند، پایان سریع
ease-out شروع سریع، پایان کند
ease-in-out شروع و پایان کند، وسط سریع

این پنج کلیدواژه، در واقع، همگی، خودشون، میان‌بری برای یک تابع دقیق‌تر و پایه‌ای‌تر هستن: cubic-bezier().

cubic-bezier(): تعریف دستی و دقیق منحنی

.box {
  transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
}

تابع cubic-bezier(x1, y1, x2, y2)، یک منحنی بزیه ریاضی رو، با چهار عدد، تعریف می‌کنه؛ این چهار عدد، مختصات دو نقطه کنترلیه که شکل دقیق منحنی سرعت رو مشخص می‌کنن. مثال بالا، دقیقاً همون مقداریه که پشت کلیدواژه ease قرار داره؛ یعنی ease، فقط یک نام مستعار برای cubic-bezier(0.25, 0.1, 0.25, 1)ست.

محدودیت‌های مقادیر ورودی

.valid {
  transition-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55);
}

در تابع cubic-bezier()، مقادیر x1 و x2، باید حتماً بین ۰ و ۱ باشن (چون این دو، محور زمان رو نشون می‌دن، و زمان، نمی‌تونه منفی یا بیشتر از کل مدت انتقال باشه). اما مقادیر y1 و y2 (که پیشرفت بصری رو نشون می‌دن)، می‌تونن از این بازه هم خارج بشن؛ همون‌طور که در این مثال می‌بینیم. این خروج از بازه، جلوه‌ای معروف به «Overshoot» می‌سازه؛ یعنی عنصر، در طول انیمیشن، از مقصد نهایی‌ش، کمی رد می‌شه و بعد، به عقب برمی‌گرده (شبیه یک فنر که کمی بیش‌ازحد کشیده می‌شه).

یک ابزار عملی برای انتخاب مقادیر

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

steps(): انتقال پلکانی، نه پیوسته

.box {
  transition-timing-function: steps(5);
}

برخلاف cubic-bezier() که یک حرکت کاملاً نرم و پیوسته می‌سازه، تابع steps(number)، انتقال رو، به تعداد مشخصی «پله» یا «فریم» گسسته، تقسیم می‌کنه؛ یعنی عنصر، به‌جای حرکت روان، در چند جهش مجزا و ناگهانی، از حالت اول به حالت آخر می‌رسه.

یک مثال بصری از steps()

.progress-bar {
  width: 0%;
  transition: width 1s steps(4);
}

.progress-bar.loaded {
  width: 100%;
}

در این مثال، عرض نوار پیشرفت، به‌جای رشد نرم از ۰٪ به ۱۰۰٪، در چهار جهش مجزا (۲۵٪، ۵۰٪، ۷۵٪، ۱۰۰٪) تغییر می‌کنه؛ هر جهش، یک‌چهارم از کل مدت زمان (۱ ثانیه) رو می‌گیره.

پارامتر دوم steps(): jump-start یا jump-end

.a { transition-timing-function: steps(4, jump-end); }
.b { transition-timing-function: steps(4, jump-start); }

پارامتر دوم اختیاری steps()، مشخص می‌کنه اولین جهش، دقیقاً در چه لحظه‌ای اتفاق بیفته: jump-end (مقدار پیش‌فرض)، یعنی جهش‌ها، در انتهای هر بازه زمانی اتفاق می‌افتن؛ jump-start، یعنی در ابتدای هر بازه.

کاربرد بسیار رایج steps(): انیمیشن Sprite Sheet

.sprite-animation {
  width: 100px;
  height: 100px;
  background-image: url("walk-sprite.png");
  background-position: 0 0;
  animation: walk 1s steps(8) infinite;
}

@keyframes walk {
  from { background-position: 0 0; }
  to { background-position: -800px 0; }
}

یکی از رایج‌ترین کاربردهای steps()، ساخت انیمیشن‌های فریم‌محور (مثل یک شخصیت در حال راه رفتن)، از روی یک تصویر واحد که شامل چند فریم پشت‌سرهمه (Sprite Sheet). با steps(8)، پس‌زمینه، به‌جای لغزیدن نرم، دقیقاً بین ۸ فریم مجزا، جهش می‌کنه؛ که این، دقیقاً همون رفتاریه که برای نمایش یک انیمیشن فریم‌به‌فریم، لازمه. جزئیات کامل @keyframes و animation رو، در فصل بعدی (Animations) بررسی می‌کنیم.

مقایسه cubic-bezier() و steps()

تابع نوع حرکت کاربرد رایج
cubic-bezier() پیوسته و نرم جلوه‌های طبیعی UI، مثل باز شدن یک منو یا تغییر رنگ
steps() گسسته و پلکانی انیمیشن‌های فریم‌محور، شمارنده‌های عددی، جلوه‌های مکانیکی

نکات مهم

  • پنج کلیدواژه آماده (linear، ease و مشابه‌ها)، همگی، در واقع، مقادیر از پیش تعریف‌شده تابع cubic-bezier() هستند.
  • در cubic-bezier()، مقادیر x باید بین 0 و 1 باشند، ولی مقادیر y می‌توانند از این بازه خارج شوند و جلوه Overshoot بسازند.
  • steps(n)، انتقال را به n پله گسسته تقسیم می‌کند و برای انیمیشن‌های فریم‌محور (مثل Sprite Sheet)، کاربرد رایجی دارد.

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

  • استفاده از steps() برای جلوه‌هایی که نیاز به حرکت نرم و طبیعی دارند، که نتیجه‌ای پرشی و ناخوشایند ایجاد می‌کند.
  • وارد کردن مقادیر x خارج از بازه 0 تا 1 در cubic-bezier()، که باعث نامعتبر شدن تابع می‌شود.
  • گیج شدن بین jump-start و jump-end در steps()، که می‌تواند لحظه دقیق هر جهش را جابه‌جا کند.

جمع‌بندی

cubic-bezier() امکان تعریف دقیق و سفارشی یک منحنی سرعت پیوسته را، فراتر از پنج کلیدواژه آماده، با چهار عدد کنترلی فراهم می‌کند؛ مقادیر y آن حتی می‌توانند از بازه 0 تا 1 خارج شوند و جلوه Overshoot بسازند. steps(n)، برخلاف آن، انتقال را به تعداد مشخصی پله گسسته تقسیم می‌کند و عمدتاً برای انیمیشن‌های فریم‌محور، مثل Sprite Sheetها، کاربرد دارد.