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:
- Interruptible — bisa dihentikan di tengah jalan dan dilanjutkan secara alami
- Natural — mengikuti hukum fisika, sehingga terasa organik
- Dynamic — responnya bisa berbeda tergantung kecepatan awal
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:
- Stiffness tinggi → gerakan cepat, agresif
- Damping rendah → banyak bounce (underdamped)
- Damping tinggi → langsung berhenti tanpa bounce (overdamped)
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.