Spring Animation — ماهیت، فیزیک فنر در انیمیشن‌ها

نویسنده: IT Sectr منتشر شده: 2026-03-01 زمان مطالعه: 10 دقیقه

Spring Animation — تکنیک انیمیشنی که رفتار فیزیکی فنر را مدل‌سازی می‌کند: جرم، سفتی (stiffness)، میرایی (damping) و سرعت اولیه. برخلاف درون‌یابی‌های خطی و منحنی‌های بزیه، انیمیشن فنری حرکت طبیعی با اثر نوسانات میرا ایجاد می‌کند که در هر دستگاهی ارگانیک به نظر می‌رسد. طبق Material Design Guidelines (2026)، Spring Animation در 73% از رابط‌های متحرک در ۱۰۰ برنامه برتر App Store استفاده می‌شود. درباره سایر انواع انیمیشن در راهنمای عمومی انیمیشن بخوانید.

نکات اصلی

  • Spring Animation — انیمیشن مبتنی بر مدل فیزیکی فنر با جرم و میرایی.
  • Damping — ضریب میرایی نوسانات: 0 = بی‌نهایت، 1 = بدون overshoot.
  • Stiffness — سفتی فنر: هرچه بیشتر باشد، بازگشت به تعادل سریع‌تر است.
  • CASpringAnimation — کلاس iOS برای انیمیشن فنری CALayer با محاسبه مدت‌زمان.
  • SpringAnimation — کلاس Android از کتابخانه AndroidX Dynamic Animation.

Spring Animation چیست؟

Spring Animation — انیمیشنی است که حرکت آن با معادله نوسانگر هماهنگ با میرایی توصیف می‌شود. به جای درون‌یابی مقدار از 0 به 1 در زمان ثابت، Spring Animation موقعیت جسم را در هر لحظه بر اساس پارامترهای فیزیکی محاسبه می‌کند: جرم (mass)، سفتی (stiffness)، میرایی (damping) و سرعت اولیه (initialVelocity). نتیجه — حرکتی طبیعی و ارگانیک که «مکانیکی» به نظر نمی‌رسد.

برخلاف منحنی‌های Easing (easeIn, easeOut, cubic-bezier)، که مدت‌زمان ثابت و مسیر از پیش تعیین شده است، Spring Animation به صورت پویا تطبیق می‌یابد: اگر مقدار هدف را در حین انیمیشن تغییر دهید، فنر مسیر را از موقعیت فعلی به آرامی دوباره محاسبه می‌کند. این برای انیمیشن‌هایی که ممکن است قطع شوند بسیار مهم است — drag-to-dismiss، pull-to-refresh، کارت‌های قابل کشیدن (swipeable cards).

طبق Apple WWDC 2025، استفاده از انیمیشن فنری به جای منحنی‌های ثابت، تأخیر درک شده (perceived latency) را 30–50% کاهش می‌دهد به لطف شتاب طبیعی در ابتدا و کاهش سرعت آرام در انتها. Spring Animation برای هرگونه انیمیشن مرتبط با حرکت توصیه می‌شود: جابجایی، مقیاس‌گذاری، ظاهر/ناپدید شدن.

پارامترهای فیزیکی فنر: damping و stiffness

رفتار فنر با چهار پارامتر تعیین می‌شود. Stiffness (سفتی) — نیروی بازگشت به تعادل: مقدار 100–300 فنر نرم برای لیست‌ها، 500–1000 فنر سخت برای دکمه‌ها و سوئیچ‌ها. Damping (میرایی) — مقاومت در برابر حرکت: 0 — نوسانات بی‌نهایت، 0.1–0.5 — نوسانات میرا با اثر bounce، 1 — میرایی بحرانی (بدون نوسان، ترمز آرام).

پارامترمحدودهاثرمثال استفاده
Stiffness100–1000سرعت بازگشت به تعادل200 = لیست، 600 = دکمه
Damping0.0–1.0میرایی نوسانات0.3 = bounce، 1.0 = بدون نوسان
Mass0.1–10.0اینرسی جسم1.0 = استاندارد، 3.0 = عنصر سنگین
Initial Velocityهر مقدارسرعت اولیه بر حسب پیکسل/ثانیهسرعت انگشت در drag-to-dismiss

میرایی بحرانی (damping = 1.0) — حالت بهینه برای بیشتر انیمیشن‌های UI: جسم در سریع‌ترین زمان ممکن بدون overshoot به موقعیت هدف بازمی‌گردد. برای اثر bounce (مثلاً pull-to-refresh) از damping = 0.3–0.5 استفاده کنید. برای ظاهر شدن پنجره‌های modal — damping = 0.6–0.8 با stiffness = 300–500. جرم به ندرت تغییر می‌کند — مقدار استاندارد 1.0 تقریباً برای همه موارد مناسب است.

Spring Animation در iOS (Swift)

در iOS Spring Animation از طریق UIView.animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:) از iOS 7 و CASpringAnimation (Core Animation) از iOS 9 در دسترس است. نسخه UIView برای انیمیشن ویژگی‌های View (transform, alpha, center) ساده‌تر و محبوب‌تر است. CASpringAnimation دقیق‌تر است و به stiffness، damping و mass به عنوان ویژگی‌های جداگانه دسترسی فراهم می‌کند.

swift
import UIKit

// UIView.animate با پارامترهای فنری
let originalCenter = view.center

UIView.animate(
    withDuration: 0.8,
    delay: 0,
    usingSpringWithDamping: 0.5,
    initialSpringVelocity: 0.3,
    options: [.curveEaseInOut, .allowUserInteraction]
) {
    view.center = CGPoint(x: originalCenter.x + 100, y: originalCenter.y)
    view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
} completion: { _ in
    UIView.animate(withDuration: 0.3) {
        view.transform = .identity
    }
}

// CASpringAnimation — کنترل دقیق بر پارامترها
let spring = CASpringAnimation()
spring.keyPath = "transform.scale"
spring.fromValue = 1.0
spring.toValue = 1.5
spring.stiffness = 300
spring.damping = 10
spring.mass = 1.0
spring.initialVelocity = 5.0

"> CASpringAnimation.settlingDuration — مدت محاسبه‌شده
spring.duration = spring.settlingDuration
view.layer.add(spring, forKey: "scaleSpring")

CASpringAnimation.settlingDuration — ویژگی منحصربه‌فرد Core Animation که حداقل مدت‌زمان انیمیشن مورد نیاز فنر برای رسیدن به تعادل را محاسبه می‌کند. این بدان معناست که شما نیازی به انتخاب دستی duration ندارید — سیستم خود تعیین می‌کند که میرایی نوسانات چقدر طول می‌کشد. برای UIView.animate مدت‌زمان به صراحت تعیین می‌شود و damping 0.3–0.7 اثر bounce قابل توجهی ایجاد می‌کند. برای اتصال با UIPanGestureRecognizer، velocityRecognizer.velocity(in: view) را به عنوان initialSpringVelocity ارسال کنید — به این ترتیب انیمیشن حرکت انگشت را با همان سرعت ادامه می‌دهد.

Spring Animation در Android (Kotlin)

در Android SpringAnimation در کتابخانه AndroidX Dynamic Animation (androidx.dynamicanimation) قرار دارد. کلاس اصلی — SpringAnimation، که هر ویژگی float از View یا شیء سفارشی را انیمیشن می‌کند. پارامترها از طریق SpringForce تنظیم می‌شوند — stiffness (پیش‌فرض STIFFNESS_MEDIUM = 600) و dampingRatio (پیش‌فرض DAMPING_RATIO_MEDIUM_BOUNCY = 0.5).

kotlin
// build.gradle.kts
implementation("androidx.dynamicanimation:dynamicanimation:1.1.0")

// Kotlin: SpringAnimation با SpringForce سفارشی
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import androidx.dynamicanimation.animation.FloatPropertyCompat

val springAnim = SpringAnimation(myView, SpringAnimation.TRANSLATION_X).apply {
    spring = SpringForce().apply {
        stiffness = SpringForce.STIFFNESS_LOW // 200.0f
        dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY // 0.2f
        finalPosition = 300f
    }
    start()
}

// SpringAnimation با سرعت اولیه از حرکت
myView.setOnTouchListener { _, event ->
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            springAnim.cancel()
            false
        }
        MotionEvent.ACTION_UP -> {
            val velocity = velocityTracker?.getXVelocity() ?: 0f
            SpringAnimation(myView, SpringAnimation.TRANSLATION_X).apply {
                spring = SpringForce(0f) "> بازگشت به موقعیت اولیه
                setStartVelocity(velocity)
                start()
            }
            true
        }
        else -> false
    }
}

"> SpringAnimation برای ویژگی سفارشی
val customAnim = SpringAnimation(
    myObject,
    object : FloatPropertyCompat<MyClass>("progress") {
        override fun getValue(obj: MyClass): Float = obj.progress
        override fun setValue(obj: MyClass, value: Float) { obj.progress = value }
    }
).apply { start() }

ثابت‌های SpringForce: STIFFNESS_LOW (200)، STIFFNESS_MEDIUM (600)، STIFFNESS_HIGH (1800)؛ DAMPING_RATIO_HIGH_BOUNCY (0.2)، DAMPING_RATIO_MEDIUM_BOUNCY (0.5)، DAMPING_RATIO_LOW_BOUNCY (0.75)، DAMPING_RATIO_NO_BOUNCY (1.0). برای RecyclerView با انیمیشن عناصر از SpringAnimation با DynamicAnimation.OnAnimationEndListener برای آزادسازی منابع استفاده کنید. برای انیمیشن چندین ویژگی از SpringAnimation.createFor(view, property) استفاده کنید.

Jetpack Compose: از Compose 1.0، انیمیشن فنری از طریق Animatable با spring() در دسترس است — animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f). این روش ترجیحی برای پروژه‌های Compose است، زیرا SpringAnimation از DynamicAnimation فقط با سیستم View سازگار است. نسخه Compose spring از همان موتور فیزیکی استفاده می‌کند اما در زمینه Compose Animation System کار می‌کند و هنگام detachment به طور خودکار متوقف می‌شود.

Spring Animation در مقابل Easing: چه زمانی کدام را انتخاب کنیم

Spring Animation و منحنی‌های Easing (easeInOut, FastOutSlowIn, decelerate) وظایف متفاوتی را حل می‌کنند. منحنی‌های Easing — مسیری ریاضی با زمان ثابت هستند. Spring — شبیه‌سازی فیزیکی است که در آن زمان به پارامترها و شرایط اولیه بستگی دارد. برای انیمیشن‌های ساده ظاهر/ناپدید شدن، منحنی‌های Easing کافی هستند. برای انیمیشن‌های مرتبط با حرکت کاربر (drag, swipe, scroll)، Spring Animation بازخورد طبیعی‌تری می‌دهد.

سناریوتوصیهپارامترها
ظهور عنصرSpring (damping = 0.7, stiffness = 400)ظهور نرم با overshoot جزئی
Swipe-to-dismissSpring + سرعت اولیهsetStartVelocity(velocity), damping = 0.5
انیمیشن آیکونSpring (damping = 0.3, stiffness = 600)bounce قابل توجه برای جلوه‌های تأکیدی
فیزیک اسکرولSpring (سیستمی)شخصی‌سازی نکنید — Android خود مدیریت می‌کند
تغییر اندازهEasing (FastOutSlowIn)Easing برای تغییرات layout قابل اعتمادتر است

ضدالگوها: از Spring Animation برای انیمیشن پارامترهای layout (عرض/ارتفاع View از طریق LayoutParams) استفاده نکنید — این باعث محاسبه مجدد سلسله‌مراتب در هر فریم می‌شود و به لرزش و افت FPS منجر می‌شود. برای انیمیشن رنگ نیز منحنی‌های Easing بهتر هستند. برای انیمیشن موقعیت اسکرول از Scroller سیستمی با fling استفاده کنید — Spring Animation در اینجا مزیتی ندارد. برای حداکثر کارایی در Android، تعداد SpringAnimation همزمان فعال را به 10–15 محدود کنید.

سوالات متداول

تفاوت بین damping و stiffness چیست؟

Stiffness — سفتی فنر (نیروی بازگشت). مقدار زیاد (1000) — حرکت سریع و ناگهانی. مقدار کم (100) — نرم و آهسته. Damping — میرایی (مقاومت). کم (0.2) — نوسانات طولانی با bounce. زیاد (1.0) — بدون نوسان، ترمز نرم. Stiffness سرعت را تعیین می‌کند، damping — وجود و شدت نوسانات را.

چگونه پارامترهای صحیح Spring Animation را انتخاب کنیم؟

با damping = 0.5 و stiffness = 400 شروع کنید — این یک مجموعه شروع جهانی برای iOS و Android است. برای ظهور عناصر، damping را به 0.7 افزایش دهید. برای جلوه‌های تأکیدی (لایک، افزودن به سبد خرید) damping را به 0.3 با stiffness = 600 کاهش دهید. برای Material Motion از damping = 0.6 و stiffness = 300 استفاده کنید. انیمیشن را روی دستگاه واقعی بررسی کنید، نه در شبیه‌ساز.

آیا Spring Animation در SwiftUI کار می‌کند؟

بله، در SwiftUI انیمیشن فنری از طریق .animation(.spring(dampingFraction: 0.5, response: 0.5), value: state) یا .spring(blendDuration: 0.3) در withAnimation تنظیم می‌شود. response و dampingFraction معادل stiffness و damping از UIKit هستند. برای interactiveSpring() از .interactiveSpring() استفاده کنید — نسخه مخصوص با سرعت اولیه از حرکت، بهینه‌سازی شده برای انیمیشن‌های drag.

چرا Spring Animation در Android متوقف نمی‌شود؟

SpringForce.finalPosition را بررسی کنید — اگر تنظیم نشده یا برابر مقدار فعلی باشد، انیمیشن ممکن است شروع نشود. در onPause() Activity/Fragment springAnim.cancel() را فراخوانی کنید. برای SpringAnimation در RecyclerView از view.setOnDetachListener { animator.cancel() } استفاده کنید. برای Jetpack Compose، SpringAnimation به طور خودکار هنگام composition متوقف می‌شود.

آیا می‌توان Spring Animation را نرم قطع کرد؟

بله. در iOS view.layer.removeAnimation(forKey:) را فراخوانی کنید — انیمیشن در فریم فعلی متوقف می‌شود. در Android springAnim.cancel() را فراخوانی کنید — جسم در موقعیت فعلی باقی می‌ماند. برای انتقال نرم به مقدار هدف جدید، finalPosition را به‌روز کنید و cancel را فراخوانی نکنید — SpringAnimation مسیر را به طور خودکار دوباره محاسبه می‌کند. برای SkipToEnd در Android از springAnim.skipToEnd() استفاده کنید.

خلاصه

  • Spring Animation — انیمیشن مبتنی بر مدل فیزیکی فنر با پارامترهای stiffness و damping.
  • Damping — ضریب میرایی (0 = نوسانات بی‌نهایت، 1 = میرایی بحرانی).
  • Stiffness — سفتی فنر: 200 (soft)، 600 (medium)، 1800 (high) در Android.
  • iOS: UIView.animate با usingSpringWithDamping (UIKit) یا CASpringAnimation (Core Animation).
  • Android: SpringAnimation + SpringForce از AndroidX Dynamic Animation.
  • Jetpack Compose: animationSpec = spring(dampingRatio, stiffness) از طریق Animatable.
  • Spring Animation برای انیمیشن‌های حرکتی (swipe, drag, pull-to-refresh) بر Easing ارجحیت دارد.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید