physics simulation
EM INDUCTION
Interactive exploration of Faraday's Law, magnetic flux, and induced EMF across four classic scenarios.
1.0 T
0.5 m/s
10
1.0 m²
3.0 Ω
Active Equations
EMF = -dΦ/dt
Φ = NBA·cos(θ)
I = EMF / R
Magnetic Flux (Wb)
Induced EMF (V)
Induced Current (A)
Power Dissipated (W)
Galvanometer
0.0 mA
Induced Current Direction
⊙
Out of page
Faraday's Law: An induced EMF is generated when magnetic flux through a loop changes with time.
Lenz's Law: The induced current flows in a direction to oppose the change in magnetic flux (its own magnetic field opposes the cause).
Current Direction: Use the right-hand rule — curl fingers in the direction of current, thumb points in direction of magnetic field created by the coil.
Lenz's Law: The induced current flows in a direction to oppose the change in magnetic flux (its own magnetic field opposes the cause).
Current Direction: Use the right-hand rule — curl fingers in the direction of current, thumb points in direction of magnetic field created by the coil.
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
// ============ SIMULATION STATE ============
const state = {
scenario: 0,
time: 0,
playing: true,
direction: 1,
B: 1.0,
velocity: 0.5,
N: 10,
A: 1.0,
R: 3.0,
history: {
flux: [],
emf: [],
current: [],
power: []
},
maxHistoryLength: 200,
simSpeed: 1
};
// Canvas references
const canvas = document.getElementById('simCanvas');
const ctx = canvas.getContext('2d');
const fluxCanvas = document.getElementById('fluxGraph');
const emfCanvas = document.getElementById('emfGraph');
const currentCanvas = document.getElementById('currentGraph');
const powerCanvas = document.getElementById('powerGraph');
const galvanometer = document.getElementById('galvanometer');
const fluxCtx = fluxCanvas.getContext('2d');
const emfCtx = emfCanvas.getContext('2d');
const currentCtx = currentCanvas.getContext('2d');
const powerCtx = powerCanvas.getContext('2d');
const galCtx = galvanometer.getContext('2d');
// ============ EVENT LISTENERS ============
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', (e) => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
e.target.classList.add('active');
state.scenario = parseInt(e.target.dataset.scenario);
state.time = 0;
state.history = { flux: [], emf: [], current: [], power: [] };
updateEquations();
});
});
document.getElementById('bFieldSlider').addEventListener('input', (e) => {
state.B = parseFloat(e.target.value);
document.getElementById('bFieldValue').textContent = state.B.toFixed(1) + ' T';
});
document.getElementById('velocitySlider').addEventListener('input', (e) => {
state.velocity = parseFloat(e.target.value);
document.getElementById('velocityValue').textContent = state.velocity.toFixed(2) + ' m/s';
});
document.getElementById('turnsSlider').addEventListener('input', (e) => {
state.N = parseInt(e.target.value);
document.getElementById('turnsValue').textContent = state.N;
});
document.getElementById('areaSlider').addEventListener('input', (e) => {
state.A = parseFloat(e.target.value);
document.getElementById('areaValue').textContent = state.A.toFixed(1) + ' m²';
});
document.getElementById('resistanceSlider').addEventListener('input', (e) => {
state.R = parseFloat(e.target.value);
document.getElementById('resistanceValue').textContent = state.R.toFixed(1) + ' Ω';
});
document.getElementById('simSpeedSlider').addEventListener('input', (e) => {
state.simSpeed = parseFloat(e.target.value);
document.getElementById('simSpeedValue').textContent = state.simSpeed.toFixed(1);
});
document.getElementById('playBtn').addEventListener('click', () => {
state.playing = !state.playing;
document.getElementById('playBtn').textContent = state.playing ? 'Pause' : 'Play';
});
document.getElementById('resetBtn').addEventListener('click', () => {
state.time = 0;
state.history = { flux: [], emf: [], current: [], power: [] };
});
document.getElementById('directionBtn').addEventListener('click', (e) => {
state.direction = 1;
document.getElementById('directionBtn').classList.add('active');
document.getElementById('reverseBtn').classList.remove('active');
e.target.classList.add('active');
});
document.getElementById('reverseBtn').addEventListener('click', (e) => {
state.direction = -1;
document.getElementById('reverseBtn').classList.add('active');
document.getElementById('directionBtn').classList.remove('active');
e.target.classList.add('active');
});
// ============ PHYSICS CALCULATIONS ============
function calculatePhysics() {
const t = state.time;
let flux = 0, emf = 0, current = 0, power = 0;
if (state.scenario === 0) {
// Magnet & Coil
const x = state.velocity * state.direction * t;
const d = Math.abs(x);
// Gaussian flux profile as magnet approaches
flux = state.N * state.A * state.B * Math.exp(-d * d / 2);
// EMF from -dΦ/dt
emf = state.N * state.A * state.B * d * Math.exp(-d * d / 2) * state.velocity * state.direction * 2;
} else if (state.scenario === 1) {
// Rotating Loop: Φ = NBA·cos(ωt)
const omega = state.velocity * 3; // Angular velocity
const theta = omega * t * state.direction;
flux = state.N * state.B * state.A * Math.cos(theta);
// EMF = -dΦ/dt = NBA·ω·sin(ωt)
emf = state.N * state.B * state.A * omega * Math.sin(theta);
} else if (state.scenario === 2) {
// Changing B field
const B_var = state.B * (0.5 + 0.5 * Math.sin(state.velocity * state.direction * t));
flux = state.N * state.A * B_var;
// dB/dt
emf = state.N * state.A * state.B * state.velocity * state.direction * 0.5 * Math.cos(state.velocity * state.direction * t);
} else if (state.scenario === 3) {
// Moving Rod: EMF = BvL
const L = Math.sqrt(state.A); // Rod length
emf = state.B * state.velocity * state.direction * L;
flux = state.B * state.A;
}
// Current from Ohm's law
current = emf / state.R;
power = current * current * state.R;
return { flux, emf, current, power };
}
function updateEquations() {
const scenarios = [
{ emf: 'EMF = -dΦ/dt', flux: 'Φ = NBA·e^(-d²/2)', current: 'I = EMF / R' },
{ emf: 'EMF = NBAω·sin(ωt)', flux: 'Φ = NBA·cos(ωt)', current: 'I = EMF / R' },
{ emf: 'EMF = NA·dB/dt', flux: 'Φ = NA·B(t)', current: 'I = EMF / R' },
{ emf: 'EMF = BvL', flux: 'Φ = BA', current: 'I = EMF / R' }
];
const s = scenarios[state.scenario];
document.getElementById('eqEmf').textContent = s.emf;
document.getElementById('eqFlux').textContent = s.flux;
}
// ============ RENDERING ============
function drawMagnetCoil() {
const t = state.time;
const x = state.velocity * state.direction * t * 50 - 150;
// Draw magnet
ctx.fillStyle = '#ff2200';
ctx.fillRect(x, 150, 40, 120);
ctx.fillStyle = '#ff4420';
ctx.fillRect(x, 150, 20, 120);
ctx.fillStyle = '#0a0a0a';
ctx.font = 'bold 12px "Bebas Neue"';
ctx.textAlign = 'center';
ctx.fillText('N', x + 10, 210);
ctx.fillText('S', x + 30, 210);
// Draw magnetic field lines
ctx.strokeStyle = 'rgba(255, 34, 0, 0.3)';
ctx.lineWidth = 1;
for (let i = -5; i <= 5; i++) {
ctx.beginPath();
for (let px = x - 100; px < x + 150; px += 5) {
const d = Math.sqrt((px - x - 20) ** 2 + (i * 30) ** 2);
const offset = 30 * Math.sin(d / 20) * Math.exp(-d / 100);
const py = i * 30 + offset;
if (px === x - 100) ctx.moveTo(px, py);
else ctx.lineTo(px, py);
}
ctx.stroke();
}
// Draw coil
ctx.strokeStyle = '#e8e0d5';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.ellipse(300, 180, 60, 30, 0, 0, Math.PI * 2);
ctx.stroke();
// Draw coil turns
for (let i = 0; i < 4; i++) {
ctx.beginPath();
ctx.ellipse(300, 180 + i * 20, 60, 10, 0, 0, Math.PI * 2);
ctx.stroke();
}
// Draw flux value
const { flux } = calculatePhysics();
ctx.fillStyle = '#00c896';
ctx.font = '14px "DM Mono"';
ctx.textAlign = 'left';
ctx.fillText(`Φ = ${flux.toFixed(2)} Wb`, 50, 430);
}
function drawRotatingLoop() {
const omega = state.velocity * 3;
const theta = omega * state.time * state.direction;
ctx.save();
ctx.translate(250, 250);
// Draw B field
ctx.fillStyle = 'rgba(255, 136, 0, 0.15)';
ctx.beginPath();
for (let x = -150; x <= 150; x += 20) {
for (let y = -150; y <= 150; y += 20) {
ctx.rect(x - 3, y - 3, 6, 6);
}
}
ctx.fill();
// Draw rotating coil in isometric
ctx.strokeStyle = '#ff2200';
ctx.lineWidth = 2;
ctx.rotate(theta);
// Front face
ctx.beginPath();
ctx.moveTo(-60, -40);
ctx.lineTo(60, -40);
ctx.lineTo(60, 40);
ctx.lineTo(-60, 40);
ctx.closePath();
ctx.stroke();
// Back edge
ctx.beginPath();
ctx.moveTo(-60, -40);
ctx.lineTo(-50, -35);
ctx.lineTo(70, -35);
ctx.lineTo(60, -40);
ctx.stroke();
ctx.restore();
// Draw flux and EMF
const { flux, emf } = calculatePhysics();
ctx.fillStyle = '#00c896';
ctx.font = '14px "DM Mono"';
ctx.textAlign = 'left';
ctx.fillText(`Φ = ${flux.toFixed(2)} Wb`, 50, 430);
ctx.fillStyle = '#ff2200';
ctx.fillText(`EMF = ${emf.toFixed(2)} V`, 50, 455);
}
function drawChangingB() {
const B_var = state.B * (0.5 + 0.5 * Math.sin(state.velocity * state.direction * state.time));
const intensity = Math.abs(B_var);
// Draw changing field
ctx.fillStyle = `rgba(255, 136, 0, ${intensity / 2})`;
ctx.beginPath();
for (let x = 100; x <= 400; x += 15) {
for (let y = 150; y <= 350; y += 15) {
if (B_var > 0) {
ctx.fillRect(x - 4, y - 4, 8, 8);
} else {
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fill();
}
}
}
// Draw coil
ctx.strokeStyle = '#e8e0d5';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.ellipse(250, 250, 80, 100, 0, 0, Math.PI * 2);
ctx.stroke();
// Draw induced current arrows
const { current } = calculatePhysics();
if (Math.abs(current) > 0.01) {
ctx.strokeStyle = current > 0 ? '#00c896' : '#ff5555';
ctx.lineWidth = 2;
const dir = current > 0 ? 1 : -1;
for (let i = 0; i < 4; i++) {
const angle = (i / 4) * Math.PI * 2;
const x1 = 250 + 80 * Math.cos(angle);
const y1 = 250 + 100 * Math.sin(angle);
const arrowLen = 15;
const nextAngle = angle + (dir * 0.3);
const x2 = x1 + arrowLen * Math.cos(nextAngle);
const y2 = y1 + arrowLen * Math.sin(nextAngle);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
// Arrowhead
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x2 - 5 * Math.cos(nextAngle - 0.5), y2 - 5 * Math.sin(nextAngle - 0.5));
ctx.lineTo(x2 - 5 * Math.cos(nextAngle + 0.5), y2 - 5 * Math.sin(nextAngle + 0.5));
ctx.closePath();
ctx.fill();
}
}
const { flux, emf } = calculatePhysics();
ctx.fillStyle = '#00c896';
ctx.font = '14px "DM Mono"';
ctx.textAlign = 'left';
ctx.fillText(`B = ${B_var.toFixed(2)} T`, 50, 430);
ctx.fillStyle = '#ff2200';
ctx.fillText(`EMF = ${emf.toFixed(2)} V`, 50, 455);
}
function drawMovingRod() {
const x = state.velocity * state.direction * state.time * 100;
// Draw B field (into page)
ctx.fillStyle = 'rgba(100, 150, 255, 0.1)';
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
ctx.beginPath();
ctx.arc(100 + i * 40, 150 + j * 40, 3, 0, Math.PI * 2);
ctx.fill();
ctx.font = '10px "DM Mono"';
ctx.fillStyle = 'rgba(100, 150, 255, 0.3)';
ctx.textAlign = 'center';
ctx.fillText('⊗', 100 + i * 40, 155 + j * 40);
}
}
// Draw rails
ctx.strokeStyle = '#555555';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(100, 200);
ctx.lineTo(400, 200);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(100, 320);
ctx.lineTo(400, 320);
ctx.stroke();
// Draw moving rod
ctx.strokeStyle = '#ff2200';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(200 + x, 200);
ctx.lineTo(200 + x, 320);
ctx.stroke();
// Draw resistance
ctx.strokeStyle = '#e8e0d5';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(100, 320);
ctx.lineTo(120, 330);
ctx.lineTo(140, 310);
ctx.lineTo(160, 330);
ctx.lineTo(180, 310);
ctx.lineTo(200, 320);
ctx.stroke();
ctx.fillStyle = '#555555';
ctx.font = '12px "DM Mono"';
ctx.textAlign = 'center';
ctx.fillText('R', 150, 350);
const { emf, current } = calculatePhysics();
ctx.fillStyle = '#ff2200';
ctx.font = '14px "DM Mono"';
ctx.textAlign = 'left';
ctx.fillText(`EMF = ${emf.toFixed(2)} V`, 50, 430);
ctx.fillStyle = '#00c896';
ctx.fillText(`I = ${current.toFixed(3)} A`, 50, 455);
}
function drawFrame() {
// Clear canvas
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#1e1e1e';
ctx.strokeRect(0, 0, canvas.width, canvas.height);
// Draw based on scenario
if (state.scenario === 0) drawMagnetCoil();
else if (state.scenario === 1) drawRotatingLoop();
else if (state.scenario === 2) drawChangingB();
else if (state.scenario === 3) drawMovingRod();
}
function drawGraph(canvasEl, data, color, yMin = -5, yMax = 5) {
const gCtx = canvasEl.getContext('2d');
const w = canvasEl.width;
const h = canvasEl.height;
gCtx.fillStyle = '#0a0a0a';
gCtx.fillRect(0, 0, w, h);
gCtx.strokeStyle = '#1e1e1e';
gCtx.strokeRect(0, 0, w, h);
if (data.length < 2) return;
// Draw grid
gCtx.strokeStyle = '#1e1e1e';
gCtx.lineWidth = 0.5;
for (let i = 0; i <= 4; i++) {
const y = (i / 4) * h;
gCtx.beginPath();
gCtx.moveTo(0, y);
gCtx.lineTo(w, y);
gCtx.stroke();
}
// Draw data line
gCtx.strokeStyle = color;
gCtx.lineWidth = 2;
gCtx.beginPath();
for (let i = 0; i < data.length; i++) {
const x = (i / data.length) * w;
const y = h - ((data[i] - yMin) / (yMax - yMin)) * h;
if (i === 0) gCtx.moveTo(x, y);
else gCtx.lineTo(x, y);
}
gCtx.stroke();
// Draw axis labels
gCtx.fillStyle = '#555555';
gCtx.font = '10px "DM Mono"';
gCtx.textAlign = 'right';
gCtx.fillText(yMax.toFixed(1), w - 5, 12);
gCtx.fillText(yMin.toFixed(1), w - 5, h - 2);
}
function drawGalvanometer(currentVal) {
const w = galvanometer.width;
const h = galvanometer.height;
const centerX = w / 2;
const centerY = h / 2;
galCtx.fillStyle = '#0a0a0a';
galCtx.fillRect(0, 0, w, h);
// Draw gauge body
galCtx.strokeStyle = '#1e1e1e';
galCtx.lineWidth = 2;
galCtx.beginPath();
galCtx.arc(centerX, centerY, 50, 0, Math.PI * 2);
galCtx.stroke();
// Draw scale
galCtx.strokeStyle = '#555555';
galCtx.lineWidth = 1;
for (let i = -180; i <= 180; i += 30) {
const rad = (i * Math.PI) / 180;
const x1 = centerX + 45 * Math.cos(rad);
const y1 = centerY + 45 * Math.sin(rad);
const x2 = centerX + 50 * Math.cos(rad);
const y2 = centerY + 50 * Math.sin(rad);
galCtx.beginPath();
galCtx.moveTo(x1, y1);
galCtx.lineTo(x2, y2);
galCtx.stroke();
}
// Draw needle
const needleAngle = Math.max(-160, Math.min(160, currentVal * 20)) * (Math.PI / 180);
galCtx.strokeStyle = currentVal > 0 ? '#00c896' : currentVal < 0 ? '#ff5555' : '#555555';
galCtx.lineWidth = 3;
galCtx.beginPath();
galCtx.moveTo(centerX, centerY);
galCtx.lineTo(
centerX + 40 * Math.cos(needleAngle),
centerY + 40 * Math.sin(needleAngle)
);
galCtx.stroke();
// Center dot
galCtx.fillStyle = '#e8e0d5';
galCtx.beginPath();
galCtx.arc(centerX, centerY, 4, 0, Math.PI * 2);
galCtx.fill();
}
// ============ ANIMATION LOOP ============
function animate() {
if (state.playing) {
state.time += 0.05 * state.simSpeed;
}
const { flux, emf, current, power } = calculatePhysics();
// Update history
state.history.flux.push(flux);
state.history.emf.push(emf);
state.history.current.push(current);
state.history.power.push(power);
if (state.history.flux.length > state.maxHistoryLength) {
state.history.flux.shift();
state.history.emf.shift();
state.history.current.shift();
state.history.power.shift();
}
// Draw main canvas
drawFrame();
// Draw graphs
drawGraph(fluxCanvas, state.history.flux, '#00c896', -3, 3);
drawGraph(emfCanvas, state.history.emf, '#ff2200', -5, 5);
drawGraph(currentCanvas, state.history.current, '#00c896', -2, 2);
drawGraph(powerCanvas, state.history.power, '#f5c518', 0, 2);
// Update galvanometer
drawGalvanometer(current);
document.getElementById('galValue').textContent = (current * 1000).toFixed(1) + ' mA';
// Update current direction
const dir = current > 0 ? '⊙' : current < 0 ? '⊗' : '·';
const dirLabel = current > 0 ? 'Out of page' : current < 0 ? 'Into page' : 'No current';
document.getElementById('currentDir').textContent = dir;
document.getElementById('currentDirLabel').textContent = dirLabel;
requestAnimationFrame(animate);
}
// Initialize
updateEquations();
animate();