Spring Parameters: mass, stiffness, damping

Spring animation không hoạt động như cubic-bezier. Thay vì một đường cong cố định với duration xác định, spring là một hệ vật lý: nó settle khi velocity về gần 0. Hiểu ba parameter của hệ này cho phép bạn thiết kế motion từ nguyên lý thay vì copy preset.

Xem thêm phần giới thiệu về spring trong Ease vs Spring.

Phương trình spring

F = -k·x - d·v
  • k = stiffness: lực kéo về điểm cân bằng
  • d = damping: lực cản (ma sát)
  • x = displacement: khoảng cách từ điểm cân bằng
  • v = velocity: vận tốc hiện tại

Mỗi frame, hệ thống tính lực F, cập nhật velocity, cập nhật vị trí. Animation kết thúc khi |x| < threshold && |v| < threshold.

Ba parameter

Stiffness (độ cứng lò xo)

Stiffness quyết định lực kéo về đích mạnh đến đâu. Stiffness cao = lò xo cứng = animation nhanh và crisp. Stiffness thấp = lò xo mềm = animation chậm và lazy.

StiffnessCảm giácDùng cho
50–100Lazy, slowContent cards, long reveals
200–300Standard, balancedMost UI elements
400–600Snappy, crispMicro-interactions, tooltips
800+Very stiffNear-instant with slight bounce

Damping (độ giảm chấn)

Damping quyết định lò xo settle nhanh hay nảy nhiều. Damping thấp = nảy nhiều (underdamped). Damping cao = không nảy, settle chậm (overdamped). Điểm giữa = critically damped (settle nhanh nhất không nảy).

Critical damping = 2 × √(stiffness × mass)
Trạng tháiDamping / CriticalBehavior
Underdamped< 1Overshoot, oscillate rồi settle
Critically damped= 1Settle nhanh nhất, không overshoot
Overdamped> 1Không overshoot, settle chậm hơn critical

Với Framer Motion, giá trị damping khoảng 10–15 cho underdamped nhẹ (bounce tự nhiên), 20–30 cho critically damped.

Mass (khối lượng)

Mass quyết định quán tính — vật nặng bao nhiêu. Mass cao = khởi động chậm, dừng muộn. Mass thấp = phản hồi tức thì.

Mass thường để mặc định (= 1) và điều chỉnh stiffness + damping để đạt feel mong muốn. Mass hữu ích nhất khi muốn “nặng” hoặc “nhẹ” mà không thay đổi bounce amount.

Tổ hợp phổ biến

Gentle bounce — toggle thumb, like button:

{ stiffness: 260, damping: 20, mass: 1 }

Snappy, no bounce — tooltip, dropdown:

{ stiffness: 400, damping: 30, mass: 1 }

Heavy, slow settle — modal, bottom sheet:

{ stiffness: 200, damping: 25, mass: 1.2 }

Playful — notification popup, celebration:

{ stiffness: 180, damping: 12, mass: 1 }

CSS vs JavaScript spring

CSS chỉ có cubic-bezier — không có spring thật. Spring được giả lập bằng control point y > 1:

/* Giả lập spring — cố định duration */
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);

Giới hạn: CSS spring luôn có duration cố định. Nếu element bị interrupt giữa chừng, animation sẽ “jump” thay vì preserve velocity.

JavaScript (Framer Motion, React Spring, GSAP) dùng vật lý thật:

// Framer Motion
animate(element, { x: 100 }, {
  type: 'spring',
  stiffness: 260,
  damping: 20
})

// React Spring
useSpring({ x: 100, config: { stiffness: 260, damping: 20 } })

JS spring có thể interrupt và resume với velocity được preserve — kéo rồi thả ở giữa chừng sẽ continue từ vận tốc hiện tại thay vì restart.

Velocity-based spring

Điểm mạnh nhất của spring thật: velocity propagation. Khi người dùng swipe một card, spring nhận velocity từ gesture và tiếp tục theo quán tính đó. Đây là lý do iOS scroll cảm giác tự nhiên — nó là spring nhận velocity từ finger velocity.

const gesture = useGesture({
  onDrag: ({ velocity, offset }) => {
    api.start({
      x: offset[0],
      config: { velocity: velocity[0], tension: 200, friction: 30 }
    })
  }
})

Khi nào dùng spring thay vì bezier

Dùng spring khi: element phản hồi direct interaction (drag, press, swipe), hoặc khi cần feel “vật lý” (toggle, like button, notification).

Dùng bezier khi: animation không phụ thuộc interaction (page transition, skeleton fade, scroll reveal) hoặc khi cần timing chính xác để sync với audio/video.

Xem thêm: Cubic Bezier chuyên sâu, Gesture Feedback Motion, Modal & Sheet Animation.