Skip to content

Spring Simulation

Build a Hooke's Law spring-mass system with force vectors, energy bars, and resonance driving.

Paste each step into main.js in the editor, then click Restart.

Step 1: Basic Spring-Mass

A mass attached to a spring, oscillating horizontally.

javascript
// 1. State Setup
if (!state.init) {
  state.x = 150;          // displacement from equilibrium
  state.v = 0;            // velocity
  state.k = 25;           // spring constant (N/m)
  state.m = 1.0;          // mass (kg)
  state.damping = 0;      // damping coefficient
  state.init = true;
}

// 2. Physics Step — Hooke's Law: F = -kx
const F_spring = -state.k * state.x;
const F_damp = -state.damping * state.v;
const F_total = F_spring + F_damp;
const a = F_total / state.m;

state.v += a * dt;
state.x += state.v * dt;

// Visual positions
const anchorX = w * 0.3;
const centerY = h * 0.5;
const massX = anchorX + state.x;

// 3. Render Return — one lines array (duplicate keys would overwrite)
return {
  lines: [
    { x1: anchorX - 30, y1: centerY - 30, x2: anchorX - 30, y2: centerY + 30, color: '#64748b', width: 3 },
    { x1: anchorX - 30, y1: centerY, x2: massX, y2: centerY, color: '#F59E0B', width: 2 },
    { x1: anchorX, y1: centerY - 25, x2: anchorX, y2: centerY + 25, color: 'rgba(255,255,255,0.15)', width: 1, dashed: true },
  ],
  particles: [
    { x: massX, y: centerY, r: 14, color: '#8B5CF6', label: 'm' },
  ],
  vectors: [
    { x: F_spring * 0.05, y: 0, ox: massX, oy: centerY - 20, color: '#EF4444', label: 'F' },
    { x: state.v * 0.3, y: 0, ox: massX, oy: centerY + 20, color: '#22C55E', label: 'v' },
  ],
  text: {
    x: state.x.toFixed(2) + ' px',
    v: state.v.toFixed(2),
    a: a.toFixed(2),
    F: F_spring.toFixed(1),
  },
};

📐 MATH CHECK Hooke's Law: F = -kx Natural frequency: ω₀ = sqrt(k/m) = sqrt(25/1) = 5 rad/s Period: T = 2π/ω₀ ≈ 1.26s The mass oscillates between x = +150 and x = -150 (amplitude preserved since there's no damping).

Step 2: Add Energy Bars

Visualize kinetic and potential energy in real-time.

javascript
// Add after the physics step:
const KE = 0.5 * state.m * state.v * state.v;
const PE = 0.5 * state.k * state.x * state.x;
const totalE = KE + PE;
const maxE = 0.5 * state.k * 150 * 150;  // max amplitude energy
const barMaxH = 120;

// Add to the return object:
bars: [
  { x: w - 80, y: h - 30 - (KE / maxE) * barMaxH, w: 30, h: (KE / maxE) * barMaxH, color: '#EF4444', label: 'KE' },
  { x: w - 40, y: h - 30 - (PE / maxE) * barMaxH, w: 30, h: (PE / maxE) * barMaxH, color: '#3B82F6', label: 'PE' },
],

💡 PRO TIP In an undamped spring, KE + PE is constant. Watch the bars trade height as the mass moves — when KE is max (at equilibrium), PE is zero, and vice versa.

Step 3: Driven Oscillation with Resonance

Add a periodic driving force: F_drive = A * sin(ω_drive * t)

javascript
if (!state.init) {
  state.x = 0;
  state.v = 0;
  state.k = 25;
  state.m = 1.0;
  state.damping = 2;          // light damping
  state.driveAmplitude = 30;  // driving force amplitude
  state.driveFreq = 5;        // driving frequency (rad/s) — try ω₀ = 5 for resonance
  state.init = true;
}

// Physics
const F_spring = -state.k * state.x;
const F_damp = -state.damping * state.v;
const F_drive = state.driveAmplitude * Math.sin(state.driveFreq * time);
const a = (F_spring + F_damp + F_drive) / state.m;

state.v += a * dt;
state.x += state.v * dt;

// Track max amplitude for bar scaling
state.maxAmp = Math.max(state.maxAmp ?? 0, Math.abs(state.x));

// ... rendering code same as Step 1, with updated text:
text: {
  'x': state.x.toFixed(1),
  'F_drive': F_drive.toFixed(1),
  'ω_drive': state.driveFreq.toFixed(1),
  'ω₀': Math.sqrt(state.k / state.m).toFixed(1),
},

📐 MATH CHECK Resonance occurs when ω_drive ≈ ω₀. At resonance, the driving force is always in phase with the velocity, pumping energy into the system. The amplitude grows until damping balances the input.

Try driveFreq = 5 (= ω₀) to see resonance. Then try driveFreq = 2 or driveFreq = 10 to see off-resonance behavior with much smaller amplitude.

Parameter Guide

ParameterEffectTry These Values
kSpring stiffness (higher = faster oscillation)10, 25, 100
mMass (higher = slower oscillation)0.5, 1, 3
dampingEnergy loss (0 = perpetual, >5 = overdamped)0, 2, 10
driveAmplitudeForce input strength10, 30, 100
driveFreqForcing frequency (match ω₀ for resonance)2, 5, 10

Extending This Example

  • Add a frequency sweep: gradually change driveFreq over time and plot amplitude vs. frequency
  • Implement a phase portrait: plot (x, v) points each frame to show the attractor
  • Add multiple coupled springs (two masses connected by springs)