Physics-based Animation — Web Animation

Physics-based Animation Physics-based animation menggunakan model matematika dari fisika nyata — pegas, gravitasi, inersia — untuk menghasilkan gerakan yang…

Physics-based Animation

Physics-based animation menggunakan model matematika dari fisika nyata — pegas, gravitasi, inersia — untuk menghasilkan gerakan yang terasa alami tanpa perlu mendefinisikan duration dan easing secara eksplisit.

Mengapa Physics Animation?

Animasi berbasis kurva (cubic-bezier) memiliki durasi tetap. Animasi berbasis fisika:

Spring Animation — Model Matematika

Pegas (spring) didefinisikan oleh tiga parameter:

F = -kx - bv

k = stiffness (kekakuan pegas)
b = damping (redaman / gesekan)
x = displacement (jarak dari posisi setimbang)
v = velocity (kecepatan)

Dalam animasi:

Spring di Framer Motion

// Spring default — terasa natural
<motion.div
  animate={{ x: 100 }}
  transition={{ type: 'spring' }}
/>

// Snappy — UI action (tombol, toggle)
<motion.div
  animate={{ x: 100 }}
  transition={{
    type: 'spring',
    stiffness: 400,
    damping: 30,
    mass: 1,
  }}
/>

// Bouncy — playful, game-like
<motion.div
  animate={{ scale: 1.2 }}
  transition={{
    type: 'spring',
    stiffness: 200,
    damping: 8,
    mass: 0.5,
  }}
/>

// Slow, heavy — large elements
<motion.div
  animate={{ y: 0 }}
  transition={{
    type: 'spring',
    stiffness: 80,
    damping: 20,
    mass: 2,
  }}
/>

Klasifikasi Spring

Berdasarkan nilai damping ratio (ζ = b / (2√(km))):

ζ < 1  → Underdamped  (bounce)
ζ = 1  → Critically damped  (berhenti persis di target)
ζ > 1  → Overdamped  (perlahan, tanpa bounce)
function classifySpring(stiffness, damping, mass = 1) {
  const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
  if (dampingRatio < 1) return 'underdamped';    // bouncy
  if (dampingRatio === 1) return 'critically-damped'; // perfect stop
  return 'overdamped';                             // slow stop
}

Simulasi Spring Manual (Euler Integration)

function simulateSpring(config) {
  const { from, to, stiffness, damping, mass = 1 } = config;
  const dt = 1 / 60; // 60fps timestep

  let position = from;
  let velocity = 0;

  const positions = [position];

  for (let i = 0; i < 100; i++) {
    const displacement = position - to;
    const springForce = -stiffness * displacement;
    const dampingForce = -damping * velocity;
    const acceleration = (springForce + dampingForce) / mass;

    velocity += acceleration * dt;
    position += velocity * dt;
    positions.push(position);

    // Konvergensi — berhenti jika sudah dekat target
    if (Math.abs(displacement) < 0.001 && Math.abs(velocity) < 0.001) break;
  }

  return positions;
}

Momentum & Inertia

// Inertia — scroll dengan momentum (Framer Motion)
<motion.div
  drag="x"
  dragConstraints={{ left: -300, right: 0 }}
  dragElastic={0.1}
  dragTransition={{
    bounceStiffness: 600,
    bounceDamping: 20,
    // Inertia saat dilepas
    power: 0.8,
    timeConstant: 700,
  }}
/>

// Use spring dengan initial velocity untuk swipe
const controls = useAnimation();

function handleSwipe(velocity) {
  controls.start({
    x: velocity > 0 ? 300 : -300,
    transition: {
      type: 'spring',
      velocity: velocity * 10,
      stiffness: 400,
      damping: 40,
    },
  });
}

Gravitasi & Parabola

// Simulasi gravitasi sederhana
class Ball {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.vx = (Math.random() - 0.5) * 8;
    this.vy = -15; // lempar ke atas
    this.gravity = 0.5;
    this.bounce = 0.7; // koefisien restitusi
  }

  update(canvasHeight) {
    this.vy += this.gravity; // gravitasi
    this.x += this.vx;
    this.y += this.vy;

    // Pantul dari lantai
    if (this.y > canvasHeight) {
      this.y = canvasHeight;
      this.vy *= -this.bounce;
      this.vx *= 0.98; // friction
    }
  }
}

Physics-based animation membutuhkan latihan untuk menemukan nilai yang tepat. Mulai dari preset library, lalu sesuaikan stiffness dan damping hingga terasa pas untuk karakter produkmu.