
Prompt
Create a webpage (physics playground) that show physics in interactive way that allow the user drag sliders to see live the changes
A system prompt was added to support web rendering
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Physics Playground</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
background-color: #1a1a1a;
color: #f0f0f0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0;
padding: 20px;
box-sizing: border-box;
height: 100vh;
overflow: hidden;
}
h1 {
margin-top: 0;
margin-bottom: 20px;
font-weight: 300;
color: #4CAF50;
}
.container {
display: flex;
flex-direction: row;
gap: 30px;
align-items: flex-start;
}
.controls {
display: flex;
flex-direction: column;
gap: 20px;
background-color: #2c2c2c;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
width: 250px;
}
.control-group {
display: flex;
flex-direction: column;
}
.control-group label {
margin-bottom: 8px;
font-size: 1rem;
display: flex;
justify-content: space-between;
}
.control-group label .value {
font-weight: bold;
color: #4CAF50;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 8px;
background: #444;
outline: none;
border-radius: 5px;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: #4CAF50;
border-radius: 50%;
cursor: pointer;
border: 2px solid #1a1a1a;
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
background: #4CAF50;
border-radius: 50%;
cursor: pointer;
border: 2px solid #1a1a1a;
}
button {
padding: 12px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s ease;
}
button:hover {
background-color: #45a049;
}
.scene-container {
border: 2px solid #333;
border-radius: 4px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
overflow: hidden; /* Ensures canvas corners are rounded with the container */
}
</style>
</head>
<body>
<div class="container">
<div id="scene-container" class="scene-container">
<!-- Matter.js canvas will be inserted here -->
</div>
<div class="controls">
<h1>Physics Playground</h1>
<div class="control-group">
<label for="gravity">Gravity: <span id="gravity-value" class="value">1.0</span></label>
<input type="range" id="gravity" min="-2" max="2" value="1" step="0.1">
</div>
<div class="control-group">
<label for="friction">Friction: <span id="friction-value" class="value">0.1</span></label>
<input type="range" id="friction" min="0" max="1" value="0.1" step="0.01">
</div>
<div class="control-group">
<label for="restitution">Bounciness: <span id="restitution-value" class="value">0.6</span></label>
<input type="range" id="restitution" min="0" max="1.5" value="0.6" step="0.05">
</div>
<button id="reset-button">Reset Simulation</button>
</div>
</div>
<!-- Matter.js library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- Module Aliases ---
const { Engine, Render, Runner, World, Bodies, Composite, Mouse, MouseConstraint } = Matter;
// --- Scene Setup ---
const sceneContainer = document.getElementById('scene-container');
const sceneWidth = 800;
const sceneHeight = 600;
// --- Engine and World ---
const engine = Engine.create();
const world = engine.world;
// --- Renderer ---
const render = Render.create({
element: sceneContainer,
engine: engine,
options: {
width: sceneWidth,
height: sceneHeight,
wireframes: false, // Set to false for solid shapes
background: '#1f1f1f'
}
});
Render.run(render);
// --- Runner ---
const runner = Runner.create();
Runner.run(runner, engine);
// --- UI Elements ---
const gravitySlider = document.getElementById('gravity');
const gravityValue = document.getElementById('gravity-value');
const frictionSlider = document.getElementById('friction');
const frictionValue = document.getElementById('friction-value');
const restitutionSlider = document.getElementById('restitution');
const restitutionValue = document.getElementById('restitution-value');
const resetButton = document.getElementById('reset-button');
// --- Initial Properties ---
let properties = {
gravity: parseFloat(gravitySlider.value),
friction: parseFloat(frictionSlider.value),
restitution: parseFloat(restitutionSlider.value)
};
// Set initial world gravity
world.gravity.y = properties.gravity;
// --- Boundary Walls ---
const wallOptions = { isStatic: true, render: { fillStyle: '#333' } };
const wallThickness = 50;
World.add(world, [
Bodies.rectangle(sceneWidth / 2, sceneHeight + wallThickness / 2, sceneWidth, wallThickness, wallOptions), // Ground
Bodies.rectangle(-wallThickness / 2, sceneHeight / 2, wallThickness, sceneHeight, wallOptions), // Left Wall
Bodies.rectangle(sceneWidth + wallThickness / 2, sceneHeight / 2, wallThickness, sceneHeight, wallOptions) // Right Wall
]);
// --- Add Shapes Function ---
const addShapes = () => {
const shapes = [];
for (let i = 0; i < 60; i++) {
const x = 50 + Math.random() * (sceneWidth - 100);
const y = -50 - Math.random() * 500; // Drop from above the screen
const radius = 10 + Math.random() * 20;
let body;
if (Math.random() > 0.6) {
body = Bodies.rectangle(x, y, radius * 1.8, radius * 1.8, {
friction: properties.friction,
restitution: properties.restitution
});
} else if (Math.random() > 0.3) {
body = Bodies.circle(x, y, radius, {
friction: properties.friction,
restitution: properties.restitution
});
} else {
body = Bodies.polygon(x, y, 3 + Math.floor(Math.random() * 5), radius, {
friction: properties.friction,
restitution: properties.restitution
});
}
shapes.push(body);
}
World.add(world, shapes);
};
// --- Mouse Interactivity ---
const mouse = Mouse.create(render.canvas);
const mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.2,
render: {
visible: false
}
}
});
World.add(world, mouseConstraint);
render.mouse = mouse; // Allows mouse to control renderer view (like zoom/pan if enabled)
// --- Update Functions ---
const updateAllBodiesProperties = () => {
Composite.allBodies(world).forEach(body => {
if (!body.isStatic) {
body.friction = properties.friction;
body.restitution = properties.restitution;
}
});
};
const resetScene = () => {
// Remove all non-static bodies from the world
const bodiesToRemove = Composite.allBodies(world).filter(body => !body.isStatic);
World.remove(world, bodiesToRemove);
// Add new shapes
addShapes();
};
// --- Event Listeners ---
gravitySlider.addEventListener('input', (e) => {
properties.gravity = parseFloat(e.target.value);
world.gravity.y = properties.gravity;
gravityValue.textContent = properties.gravity.toFixed(2);
});
frictionSlider.addEventListener('input', (e) => {
properties.friction = parseFloat(e.target.value);
frictionValue.textContent = properties.friction.toFixed(2);
updateAllBodiesProperties();
});
restitutionSlider.addEventListener('input', (e) => {
properties.restitution = parseFloat(e.target.value);
restitutionValue.textContent = properties.restitution.toFixed(2);
updateAllBodiesProperties();
});
resetButton.addEventListener('click', resetScene);
// --- Initial Scene Population ---
addShapes();
});
</script>
</body>
</html>
```Here's a complete HTML file that creates an interactive physics playground with projectile motion and pendulum simulations. You can copy this into a .html file and open it in a browser:
```html
<!DOCTYPE html>
<html>
<head>
<title>Physics Playground</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 20px auto;
padding: 20px;
}
.control-panel {
margin: 20px 0;
padding: 15px;
background: #f0f0f0;
border-radius: 8px;
}
.slider-container {
margin: 10px 0;
}
canvas {
border: 1px solid #333;
background: #fff;
}
.slider-container label {
display: inline-block;
width: 150px;
}
</style>
</head>
<body>
<h1>Physics Playground</h1>
<div class="control-panel">
<h3>Projectile Motion</h3>
<div class="slider-container">
<label>Initial Velocity (m/s):</label>
<input type="range" id="velocity" min="1" max="50" value="20" step="1">
<span id="velocity-value">20</span>
</div>
<div class="slider-container">
<label>Launch Angle (°):</label>
<input type="range" id="angle" min="0" max="90" value="45" step="1">
<span id="angle-value">45</span>
</div>
</div>
<div class="control-panel">
<h3>Pendulum</h3>
<div class="slider-container">
<label>Length (m):</label>
<input type="range" id="length" min="1" max="10" value="5" step="0.1">
<span id="length-value">5</span>
</div>
<div class="slider-container">
<label>Initial Angle (°):</label>
<input type="range" id="pendulum-angle" min="0" max="90" value="30" step="1">
<span id="pendulum-angle-value">30</span>
</div>
</div>
<div class="control-panel">
<div class="slider-container">
<label>Gravity (m/s²):</label>
<input type="range" id="gravity" min="1" max="20" value="9.8" step="0.1">
<span id="gravity-value">9.8</span>
</div>
</div>
<canvas id="simulation" width="800" height="500"></canvas>
<script>
// Get canvas and context
const canvas = document.getElementById('simulation');
const ctx = canvas.getContext('2d');
// Physics parameters
let params = {
velocity: 20,
angle: 45 * Math.PI / 180,
length: 5,
pendulumAngle: 30 * Math.PI / 180,
gravity: 9.8
};
// Update values display
function updateDisplays() {
document.getElementById('velocity-value').textContent = params.velocity;
document.getElementById('angle-value').textContent = params.angle * 180 / Math.PI;
document.getElementById('length-value').textContent = params.length;
document.getElementById('pendulum-angle-value').textContent = params.pendulumAngle * 180 / Math.PI;
document.getElementById('gravity-value').textContent = params.gravity;
}
// Event listeners for sliders
document.getElementById('velocity').addEventListener('input', function() {
params.velocity = parseFloat(this.value);
updateDisplays();
});
document.getElementById('angle').addEventListener('input', function() {
params.angle = parseFloat(this.value) * Math.PI / 180;
updateDisplays();
});
document.getElementById('length').addEventListener('input', function() {
params.length = parseFloat(this.value);
updateDisplays();
});
document.getElementById('pendulum-angle').addEventListener('input', function() {
params.pendulumAngle = parseFloat(this.value) * Math.PI / 180;
updateDisplays();
});
document.getElementById('gravity').addEventListener('input', function() {
params.gravity = parseFloat(this.value);
updateDisplays();
});
// Projectile motion simulation
let projectileTime = 0;
function drawProjectile() {
const v0 = params.velocity;
const angle = params.angle;
const g = params.gravity;
// Clear previous projectile
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw trajectory
ctx.beginPath();
ctx.setLineDash([5, 5]);
ctx.strokeStyle = 'blue';
for (let t = 0; t <= 10; t += 0.1) {
const x = v0 * Math.cos(angle) * t;
const y = canvas.height - (v0 * Math.sin(angle) * t - 0.5 * g * t * t);
if (t === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
// Stop drawing when projectile hits ground
if (y >= canvas.height) break;
}
ctx.stroke();
ctx.setLineDash([]);
// Draw current position
const currentX = v0 * Math.cos(angle) * projectileTime;
const currentY = canvas.height - (v0 * Math.sin(angle) * projectileTime - 0.5 * g * projectileTime * projectileTime);
ctx.beginPath();
ctx.fillStyle = 'red';
ctx.arc(currentX, currentY, 10, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// Pendulum simulation
let pendulumAngle = params.pendulumAngle;
let pendulumVelocity = 0;
function drawPendulum() {
const length = params.length;
const g = params.gravity;
const pivotX = canvas.width - 100;
const pivotY = 100;
// Calculate angular acceleration
const angularAcceleration = -g / length * Math.sin(pendulumAngle);
// Update velocity and angle (Euler integration)
pendulumVelocity += angularAcceleration * 0.05;
pendulumAngle += pendulumVelocity * 0.05;
// Draw pendulum
ctx.save();
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
ctx.moveTo(pivotX, pivotY);
const bobX = pivotX + length * 30 * Math.sin(pendulumAngle);
const bobY = pivotY + length * 30 * Math.cos(pendulumAngle);
ctx.lineTo(bobX, bobY);
ctx.stroke();
// Draw bob
ctx.beginPath();
ctx.fillStyle = 'green';
ctx.arc(bobX, bobY, 15, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// Animation loop
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update projectile time
projectileTime += 0.1;
if (projectileTime > 10) projectileTime = 0;
drawProjectile();
drawPendulum();
requestAnimationFrame(animate);
}
// Start animation
animate();
updateDisplays();
</script>
</body>
</html>
```
This code creates a physics playground with:
1. **Projectile Motion Simulation**:
- Adjustable initial velocity (1-50 m/s)
- Adjustable launch angle (0-90 degrees)
- Real-time trajectory visualization
- Red dot shows current position
2. **Pendulum Simulation**:
- Adjustable length (1-10 meters)
- Adjustable initial angle (0-90 degrees)
- Real-time pendulum motion
- Green ball shows pendulum bob
3. **Shared Controls**:
- Gravity adjustment (1-20 m/s²) affects both simulations
Features:
- Real-time updates as you move sliders
- Visual trajectory for projectile
- Dynamic pendulum motion with realistic physics
- Clean, responsive interface
- Color-coded elements for easy identification
The simulation uses:
- HTML5 Canvas for rendering
- RequestAnimationFrame for smooth animation
- Simple Euler integration for pendulum physics
- Parametric equations for projectile motion
You can modify parameters like:
- Time steps in the animation loop
- Scale factors for visualization
- Add more physics simulations (springs, collisions, etc.)
- Change colors and styling
- Add additional parameters like air resistance or mass```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Physics Playground</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: 'Segoe UI', Arial, sans-serif;
background: #f2f3f7;
margin: 0;
padding: 0;
color: #212335;
}
.container {
max-width: 800px;
margin: 40px auto;
background: #fff;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0,0,0,0.09);
padding: 40px 24px 32px 24px;
}
h1 {
text-align: center;
margin-top: 0;
letter-spacing: 1px;
font-weight: 800;
}
.section {
margin: 32px 0 0 0;
padding-top: 8px;
}
.slider-group {
display: flex;
align-items: center;
gap: 24px;
margin: 16px 0;
}
label {
min-width: 130px;
font-size: 1rem;
}
.value {
font-family: monospace;
color: #2274a5;
font-weight: 700;
min-width: 64px;
display: inline-block;
text-align: left;
}
input[type="range"] {
width: 160px;
accent-color: #2274a5;
}
canvas {
background: #e7ebf0;
border-radius: 12px;
display: block;
margin: 0 auto 20px auto;
box-shadow: 0 2px 16px rgba(34,116,165,.04);
}
.info {
font-size: 0.96em;
margin-bottom: 4px;
color: #55576d;
text-align: center;
}
.footer {
text-align: center;
margin: 40px 0 0 0;
color: #95a5cc;
font-size: 0.9rem;
}
</style>
</head>
<body>
<div class="container">
<h1>Physics Playground</h1>
<p class="info">
Experiment with basic projectile motion.<br>
Adjust launch angle, velocity, and gravity to watch physics in action!
</p>
<canvas id="simCanvas" width="700" height="300"></canvas>
<div class="section">
<div class="slider-group">
<label for="angleSlider">Launch Angle (°)</label>
<input type="range" id="angleSlider" min="10" max="80" value="45">
<span class="value" id="angleValue">45°</span>
</div>
<div class="slider-group">
<label for="velocitySlider">Initial Velocity (m/s)</label>
<input type="range" id="velocitySlider" min="10" max="60" value="30">
<span class="value" id="velocityValue">30 m/s</span>
</div>
<div class="slider-group">
<label for="gravitySlider">Gravity (m/s²)</label>
<input type="range" id="gravitySlider" min="2" max="20" value="9.8" step="0.1">
<span class="value" id="gravityValue">9.8 m/s²</span>
</div>
</div>
<div class="section" style="margin-top:36px">
<strong>Statistics:</strong>
<div>Time of Flight: <span id="flightTime">0.0</span> s | Max Height: <span id="maxHeight">0.0</span> m | Range: <span id="range">0.0</span> m</div>
</div>
<div class="footer">
🚀 Interactive physics: projectile motion in 2D.<br>
(Sliders = real-time feedback!)
</div>
</div>
<script>
// -- INITIAL SETUP --
const canvas = document.getElementById('simCanvas');
const ctx = canvas.getContext('2d');
// Sliders and values
const angleSlider = document.getElementById('angleSlider');
const velocitySlider = document.getElementById('velocitySlider');
const gravitySlider = document.getElementById('gravitySlider');
const angleValue = document.getElementById('angleValue');
const velocityValue = document.getElementById('velocityValue');
const gravityValue = document.getElementById('gravityValue');
const flightTime = document.getElementById('flightTime');
const maxHeight = document.getElementById('maxHeight');
const rangeVal = document.getElementById('range');
// --- Physical constants (SI units) ---
let launchAngle = parseFloat(angleSlider.value); // deg
let velocity = parseFloat(velocitySlider.value); // m/s
let gravity = parseFloat(gravitySlider.value); // m/s²
// -- Canvas scaling
const marginX = 60, marginY = 36;
const drawWidth = canvas.width - marginX * 2;
const drawHeight = canvas.height - marginY * 2;
// --- Update UI labels
function updateLabels() {
angleValue.textContent = angleSlider.value + '°';
velocityValue.textContent = velocitySlider.value + ' m/s';
gravityValue.textContent = gravitySlider.value + ' m/s²';
}
// --- Calculate projectile motion path ---
function computeProjectileData(angle, v0, g) {
const rad = angle * Math.PI / 180;
const v0x = v0 * Math.cos(rad);
const v0y = v0 * Math.sin(rad);
// Trajectory parameters
const tFlight = (2 * v0y) / g;
const hMax = (v0y * v0y) / (2 * g);
const r = v0x * tFlight;
// Path points for animation
const points = [];
const steps = 100;
for(let i = 0; i <= steps; i++) {
const t = tFlight * (i / steps);
const x = v0x * t;
const y = v0y * t - 0.5 * g * t * t; // y above ground
points.push({ x, y });
}
return { points, tFlight, hMax, r };
}
// --- Scaling function to fit trajectory on canvas ---
function getWorldToCanvas(r, h) {
// r = range, h = max height
const scaleX = drawWidth / (r > 8 ? r : 8); // avoid division by small r
const scaleY = drawHeight / ((h > 3 ? h : 3) * 1.2);
return {
toCanvas: ({x, y}) => ({
x: marginX + x * scaleX,
y: canvas.height - marginY - y * scaleY // y down
}),
scaleX,
scaleY
};
}
// --- Draw simulation onto canvas ---
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Get current values
launchAngle = parseFloat(angleSlider.value);
velocity = parseFloat(velocitySlider.value);
gravity = parseFloat(gravitySlider.value);
// Calculate trajectory
const { points, tFlight, hMax, r } = computeProjectileData(launchAngle, velocity, gravity);
// Compute scaling
const scaler = getWorldToCanvas(r, hMax);
// Draw ground
ctx.strokeStyle = '#a7b6cc';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(marginX - 12, canvas.height - marginY);
ctx.lineTo(canvas.width - marginX + 12, canvas.height - marginY);
ctx.stroke();
// Draw trajectory
ctx.strokeStyle = '#2274a5';
ctx.lineWidth = 3;
ctx.beginPath();
points.forEach((pt, i) => {
const { x, y } = scaler.toCanvas(pt);
if(i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.stroke();
// Draw projectile at apex
const maxYindex = points.reduce((a, c, i, arr) => c.y > arr[a].y ? i : a, 0);
const { x: xMax, y: yMax } = scaler.toCanvas(points[maxYindex]);
ctx.save();
ctx.strokeStyle = '#476bad';
ctx.globalAlpha = 0.14;
ctx.beginPath();
ctx.ellipse(xMax, yMax, 36, 12, 0, 0, 2 * Math.PI);
ctx.fillStyle = '#2274a5';
ctx.fill();
ctx.restore();
// Draw projectile (at launch)
const ball = scaler.toCanvas(points[0]);
ctx.beginPath();
ctx.arc(ball.x, ball.y, 12, 0, 2 * Math.PI);
ctx.fillStyle = '#2274a5';
ctx.shadowColor = '#1787db';
ctx.shadowBlur = 8;
ctx.fill();
ctx.shadowBlur = 0;
// Draw projectile (at apex)
ctx.beginPath();
ctx.arc(xMax, yMax, 8, 0, 2 * Math.PI);
ctx.fillStyle = '#14be86';
ctx.fill();
// Draw projectile (at landing)
const last = scaler.toCanvas(points[points.length-1]);
ctx.beginPath();
ctx.arc(last.x, last.y, 9, 0, 2 * Math.PI);
ctx.fillStyle = '#fbae16';
ctx.fill();
// Legends
ctx.font = "bold 15px Segoe UI, Arial";
ctx.fillStyle = "#2274a5";
ctx.textAlign = 'left';
ctx.fillText("Launch", ball.x + 16, ball.y);
ctx.fillText("Max Height", xMax + 10, yMax - 10);
ctx.fillText("Landing", last.x + 16, last.y);
// Dotted line for max height
ctx.setLineDash([6, 5]);
ctx.strokeStyle = '#14be8611';
ctx.beginPath();
ctx.moveTo(xMax, canvas.height - marginY - 8);
ctx.lineTo(xMax, yMax);
ctx.stroke();
ctx.setLineDash([]);
// Draw axes (optional)
ctx.strokeStyle = "#b6bdc6";
ctx.beginPath();
ctx.moveTo(marginX, marginY - 6);
ctx.lineTo(marginX, canvas.height - marginY + 5);
ctx.stroke();
}
// --- Update stats readout ---
function updateStats() {
const { tFlight, hMax, r } = computeProjectileData(launchAngle, velocity, gravity);
flightTime.textContent = tFlight.toFixed(2);
maxHeight.textContent = hMax.toFixed(2);
rangeVal.textContent = r.toFixed(2);
}
// --- Animate projectile motion (for extra fun) ---
let lastSettings = {};
let animFrame;
function animateProjectile() {
cancelAnimationFrame(animFrame);
// If settings changed, ensure quick render and no artifact
const settings = {
angle: angleSlider.value,
velocity: velocitySlider.value,
gravity: gravitySlider.value
};
const changed = JSON.stringify(settings) !== JSON.stringify(lastSettings);
if(!changed) { animFrame = requestAnimationFrame(animateProjectile); return; }
lastSettings = settings;
// Animate "ghost" projectile along the path in a faint color
const { points, tFlight } = computeProjectileData(launchAngle, velocity, gravity);
render();
// Animate along the path
let t = 0;
const steps = points.length;
function frame() {
render();
// Draw current projectile position
const frac = t/steps;
const idx = Math.floor(frac * (steps-1));
const pt = points[idx];
if(pt) {
const scaler = getWorldToCanvas(points[points.length-1].x, Math.max(...points.map(p => p.y)));
const {x, y} = scaler.toCanvas(pt);
ctx.beginPath();
ctx.arc(x, y, 13, 0, 2 * Math.PI);
ctx.globalAlpha = 0.5;
ctx.fillStyle = "#14be86";
ctx.fill();
ctx.globalAlpha = 1.0;
}
t++;
if(t <= steps) {
animFrame = requestAnimationFrame(frame);
} else {
animFrame = requestAnimationFrame(animateProjectile);
}
}
frame();
}
// --- Main update ---
function updateAll() {
updateLabels();
updateStats();
render();
animateProjectile();
}
angleSlider.addEventListener('input', updateAll);
velocitySlider.addEventListener('input', updateAll);
gravitySlider.addEventListener('input', updateAll);
// Initial render
updateAll();
</script>
</body>
</html>
```
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pendulum Physics Playground</title>
<style>
canvas {
display: block;
margin: 20px auto;
border: 1px solid #ccc;
}
.controls {
margin: 20px auto;
text-align: center;
}
input[type="range"] {
width: 300px;
}
</style>
</head>
<body>
<canvas id="pendulumCanvas" width="600" height="400"></canvas>
<div class="controls">
<label for="lengthSlider">Length (meters): <span id="lengthValue">1.50</span></label><br>
<input type="range" min="0.1" max="3" step="0.01" id="lengthSlider" value="1.5"><br><br>
<label for="gravitySlider">Gravity (m/s²): <span id="gravityValue">9.80</span></label><br>
<input type="range" min="0.1" max="20" step="0.01" id="gravitySlider" value="9.8">
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.getElementById('pendulumCanvas');
const ctx = canvas.getContext('2d');
const lengthSlider = document.getElementById('lengthSlider');
const gravitySlider = document.getElementById('gravitySlider');
const lengthValue = document.getElementById('lengthValue');
const gravityValue = document.getElementById('gravityValue');
let L = parseFloat(lengthSlider.value);
let g = parseFloat(gravitySlider.value);
let angle = Math.PI / 4; // 45 degrees in radians
let angularVelocity = 0;
let lastTime = null;
lengthValue.textContent = L.toFixed(2);
gravityValue.textContent = g.toFixed(2);
lengthSlider.addEventListener('input', function() {
L = parseFloat(this.value);
lengthValue.textContent = L.toFixed(2);
});
gravitySlider.addEventListener('input', function() {
g = parseFloat(this.value);
gravityValue.textContent = g.toFixed(2);
});
function drawPendulum() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const pixelsPerMeter = 100;
const pivotX = canvas.width / 2;
const pivotY = 0;
const bobX = pivotX + pixelsPerMeter * L * Math.sin(angle);
const bobY = pivotY + pixelsPerMeter * L * Math.cos(angle);
ctx.beginPath();
ctx.moveTo(pivotX, pivotY);
ctx.lineTo(bobX, bobY);
ctx.stroke();
ctx.beginPath();
ctx.arc(bobX, bobY, 20, 0, 2 * Math.PI);
ctx.fillStyle = '#0095DD';
ctx.fill();
ctx.beginPath();
ctx.arc(bobX, bobY, 20, 0, 2 * Math.PI);
ctx.strokeStyle = '#333';
ctx.stroke();
}
function updatePendulum(timestamp) {
if (lastTime === null) {
lastTime = timestamp;
}
const deltaT = (timestamp - lastTime) / 1000;
lastTime = timestamp;
const angularAcceleration = (-g / L) * Math.sin(angle);
angularVelocity += angularAcceleration * deltaT;
angle += angularVelocity * deltaT;
drawPendulum();
requestAnimationFrame(updatePendulum);
}
requestAnimationFrame(updatePendulum);
});
</script>
</body>
</html>
``````html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Physics Playground</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f0f0f0;
}
h1 {
text-align: center;
}
#canvas-container {
width: 800px;
height: 600px;
margin: 0 auto;
border: 1px solid #ccc;
background-color: white;
}
#controls {
margin: 20px auto;
width: 800px;
text-align: center;
}
.slider-container {
margin: 10px;
}
label {
display: block;
margin-bottom: 5px;
}
</style>
</head>
<body>
<h1>Physics Playground</h1>
<div id="canvas-container"></div>
<div id="controls">
<div class="slider-container">
<label for="gravity">Gravity (0 to 2): <span id="gravity-value">1</span></label>
<input type="range" id="gravity" min="0" max="2" step="0.1" value="1">
</div>
<div class="slider-container">
<label for="friction">Air Friction (0 to 0.1): <span id="friction-value">0.01</span></label>
<input type="range" id="friction" min="0" max="0.1" step="0.001" value="0.01">
</div>
<div class="slider-container">
<label for="ball-count">Number of Balls (1 to 20): <span id="ball-count-value">5</span></label>
<input type="range" id="ball-count" min="1" max="20" step="1" value="5">
</div>
<button id="reset">Reset Simulation</button>
</div>
<script>
// Matter.js setup
const Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
Bodies = Matter.Bodies,
Composite = Matter.Composite,
Mouse = Matter.Mouse,
MouseConstraint = Matter.MouseConstraint;
let engine, render, runner, world;
function initSimulation(gravityY = 1, frictionAir = 0.01, ballCount = 5) {
// Clear existing if any
if (engine) {
Engine.clear(engine);
Render.stop(render);
Runner.stop(runner);
render.canvas.remove();
}
// Create engine and world
engine = Engine.create();
world = engine.world;
engine.gravity.y = gravityY;
// Create renderer
render = Render.create({
element: document.getElementById('canvas-container'),
engine: engine,
options: {
width: 800,
height: 600,
wireframes: false,
background: '#ffffff'
}
});
// Create runner
runner = Runner.create();
Runner.run(runner, engine);
Render.run(render);
// Add mouse control
const mouse = Mouse.create(render.canvas);
const mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.2,
render: { visible: false }
}
});
Composite.add(world, mouseConstraint);
render.mouse = mouse;
// Add ground and walls
const ground = Bodies.rectangle(400, 610, 810, 60, { isStatic: true });
const leftWall = Bodies.rectangle(-10, 300, 60, 600, { isStatic: true });
const rightWall = Bodies.rectangle(810, 300, 60, 600, { isStatic: true });
Composite.add(world, [ground, leftWall, rightWall]);
// Add balls
for (let i = 0; i < ballCount; i++) {
const ball = Bodies.circle(
Math.random() * 700 + 50,
Math.random() * 500 + 50,
30,
{
frictionAir: frictionAir,
restitution: 0.8,
render: { fillStyle: `hsl(${Math.random() * 360}, 100%, 50%)` }
}
);
Composite.add(world, ball);
}
}
// Initial setup
initSimulation();
// Slider event listeners
const gravitySlider = document.getElementById('gravity');
const gravityValue = document.getElementById('gravity-value');
gravitySlider.addEventListener('input', () => {
const val = parseFloat(gravitySlider.value);
gravityValue.textContent = val;
engine.gravity.y = val;
});
const frictionSlider = document.getElementById('friction');
const frictionValue = document.getElementById('friction-value');
frictionSlider.addEventListener('input', () => {
const val = parseFloat(frictionSlider.value);
frictionValue.textContent = val;
Composite.allBodies(world).forEach(body => {
if (!body.isStatic) {
body.frictionAir = val;
}
});
});
const ballCountSlider = document.getElementById('ball-count');
const ballCountValue = document.getElementById('ball-count-value');
ballCountSlider.addEventListener('input', () => {
const val = parseInt(ballCountSlider.value);
ballCountValue.textContent = val;
initSimulation(engine.gravity.y, parseFloat(frictionSlider.value), val);
});
// Reset button
document.getElementById('reset').addEventListener('click', () => {
initSimulation(parseFloat(gravitySlider.value), parseFloat(frictionSlider.value), parseInt(ballCountSlider.value));
});
</script>
</body>
</html>
```# Physics Playground: Interactive Simulations
Here's a complete HTML page with interactive physics simulations that allow users to control parameters with sliders:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Physics Playground</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #2c3e50;
text-align: center;
}
.simulation-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 30px;
}
.simulation {
flex: 1;
min-width: 300px;
border: 1px solid #ddd;
padding: 15px;
border-radius: 8px;
background: #f9f9f9;
}
canvas {
background: white;
border: 1px solid #ddd;
margin-bottom: 10px;
width: 100%;
}
.controls {
margin-top: 10px;
}
.control-group {
margin-bottom: 10px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #2c3e50;
}
input[type="range"] {
width: 100%;
}
.value-display {
font-size: 0.8em;
color: #7f8c8d;
text-align: right;
}
</style>
</head>
<body>
<div class="container">
<h1>Physics Playground</h1>
<div class="simulation-container">
<div class="simulation">
<h3>Projectile Motion</h3>
<canvas id="projectileCanvas" width="400" height="300"></canvas>
<div class="controls">
<div class="control-group">
<label for="velocity">Initial Velocity (m/s)</label>
<input type="range" id="velocity" min="5" max="50" value="20" step="1">
<div class="value-display"><span id="velocityValue">20</span> m/s</div>
</div>
<div class="control-group">
<label for="angle">Launch Angle (degrees)</label>
<input type="range" id="angle" min="0" max="90" value="45" step="1">
<div class="value-display"><span id="angleValue">45</span>°</div>
</div>
<div class="control-group">
<label for="gravity">Gravity (m/s²)</label>
<input type="range" id="gravity" min="1" max="20" value="9.8" step="0.1">
<div class="value-display"><span id="gravityValue">9.8</span> m/s²</div>
</div>
<button id="resetProjectile">Reset</button>
</div>
</div>
<div class="simulation">
<h3>Spring Pendulum</h3>
<canvas id="springCanvas" width="400" height="300"></canvas>
<div class="controls">
<div class="control-group">
<label for="springConstant">Spring Constant (k)</label>
<input type="range" id="springConstant" min="0.1" max="2" value="0.5" step="0.1">
<div class="value-display"><span id="springConstantValue">0.5</span></div>
</div>
<div class="control-group">
<label for="mass">Mass (kg)</label>
<input type="range" id="mass" min="1" max="20" value="10" step="1">
<div class="value-display"><span id="massValue">10</span> kg</div>
</div>
<div class="control-group">
<label for="damping">Damping</label>
<input type="range" id="damping" min="0" max="0.5" value="0.01" step="0.01">
<div class="value-display"><span id="dampingValue">0.01</span></div>
</div>
<button id="resetSpring">Reset</button>
</div>
</div>
</div>
<div class="simulation" style="width: 100%;">
<h3>Collision Lab</h3>
<canvas id="collisionCanvas" width="900" height="300"></canvas>
<div class="controls">
<div class="control-group" style="flex: 1;">
<label for="restitution">Restitution (elasticity)</label>
<input type="range" id="restitution" min="0" max="1" value="0.7" step="0.1">
<div class="value-display"><span id="restitutionValue">0.7</span></div>
</div>
<div class="control-group" style="flex: 1;">
<label for="friction">Friction</label>
<input type="range" id="friction" min="0" max="0.2" value="0.01" step="0.01">
<div class="value-display"><span id="frictionValue">0.01</span></div>
</div>
<button id="addBall" style="margin-left: 20px;">Add Ball</button>
<button id="resetCollision">Reset</button>
</div>
</div>
</div>
<script>
// Projectile Motion Simulation
const projectileCanvas = document.getElementById('projectileCanvas');
const projectileCtx = projectileCanvas.getContext('2d');
let velocity = 20;
let angle = 45;
let gravity = 9.8;
let projectilePath = [];
let projectileTime = 0;
let projectileInterval;
function updateProjectileValues() {
document.getElementById('velocityValue').textContent = velocity;
document.getElementById('angleValue').textContent = angle;
document.getElementById('gravityValue').textContent = gravity;
}
function drawProjectile() {
projectileCtx.clearRect(0, 0, projectileCanvas.width, projectileCanvas.height);
// Draw ground
projectileCtx.beginPath();
projectileCtx.moveTo(0, projectileCanvas.height - 50);
projectileCtx.lineTo(projectileCanvas.width, projectileCanvas.height - 50);
projectileCtx.strokeStyle = '#34495e';
projectileCtx.lineWidth = 2;
projectileCtx.stroke();
// Draw projectile path
if (projectilePath.length > 1) {
projectileCtx.beginPath();
projectileCtx.moveTo(projectilePath[0].x, projectileCanvas.height - 50 - projectilePath[0].y);
for (let i = 1; i < projectilePath.length; i++) {
projectileCtx.lineTo(projectilePath[i].x, projectileCanvas.height - 50 - projectilePath[i].y);
}
projectileCtx.strokeStyle = '#e74c3c';
projectileCtx.lineWidth = 2;
projectileCtx.stroke();
}
// Draw projectile
if (projectilePath.length > 0 && projectileTime <= (2 * velocity * Math.sin(angle * Math.PI / 180)) / gravity) {
const pos = projectilePath[projectilePath.length - 1];
projectileCtx.beginPath();
projectileCtx.arc(pos.x, projectileCanvas.height - 50 - pos.y, 8, 0, Math.PI * 2);
projectileCtx.fillStyle = '#3498db';
projectileCtx.fill();
}
}
function resetProjectile() {
clearInterval(projectileInterval);
projectilePath = [];
projectileTime = 0;
const radians = angle * Math.PI / 180;
const vx = velocity * Math.cos(radians);
const vy = velocity * Math.sin(radians);
projectileInterval = setInterval(() => {
projectileTime += 0.05;
if (projectileTime <= (2 * velocity * Math.sin(radians)) / gravity) {
const x = vx * projectileTime;
const y = vy * projectileTime - 0.5 * gravity * projectileTime * projectileTime;
projectilePath.push({x, y});
if (x > projectileCanvas.width) {
clearInterval(projectileInterval);
}
drawProjectile();
} else {
clearInterval(projectileInterval);
}
}, 50);
drawProjectile();
}
// Spring Pendulum Simulation
const springCanvas = document.getElementById('springCanvas');
const springCtx = springCanvas.getContext('2d');
// Matter.js setup for spring pendulum
const springEngine = Matter.Engine.create();
const springWorld = springEngine.world;
let springConstant = 0.5;
let mass = 10;
let damping = 0.01;
let springAnchor, springBody, springConstraint;
function updateSpringValues() {
document.getElementById('springConstantValue').textContent = springConstant;
document.getElementById('massValue').textContent = mass;
document.getElementById('dampingValue').textContent = damping;
}
function createSpringSystem() {
// Clear existing bodies
if (springAnchor) Matter.Composite.remove(springWorld, springAnchor);
if (springBody) Matter.Composite.remove(springWorld, springBody);
if (springConstraint) Matter.Composite.remove(springWorld, springConstraint);
// Create anchor
springAnchor = Matter.Bodies.rectangle(springCanvas.width / 2, 50, 50, 20, {
isStatic: true,
render: { fillStyle: '#34495e' }
});
// Create pendulum body
springBody = Matter.Bodies.circle(springCanvas.width / 2, 150, Math.sqrt(mass) * 2, {
render: { fillStyle: '#3498db' },
mass: mass
});
// Create spring constraint
springConstraint = Matter.Constraint.create({
pointA: { x: springAnchor.position.x, y: springAnchor.position.y + 10 },
bodyB: springBody,
pointB: { x: 0, y: 0 },
stiffness: springConstant,
damping: damping,
render: {
type: 'spring',
stiffness: springConstant,
damping: damping,
lineWidth: 2,
strokeStyle: '#7f8c8d'
}
});
Matter.Composite.add(springWorld, [springAnchor, springBody, springConstraint]);
// Pull the bob slightly to start oscillation
Matter.Body.applyForce(springBody, springBody.position, { x: 0.05 * mass, y: 0 });
}
function renderSpring() {
springCtx.clearRect(0, 0, springCanvas.width, springCanvas.height);
// Render all bodies
for (let i = 0; i < springWorld.bodies.length; i++) {
const body = springWorld.bodies[i];
if (body.render.visible) {
const vertices = body.vertices;
springCtx.beginPath();
springCtx.moveTo(vertices[0].x, vertices[0].y);
for (let j = 1; j < vertices.length; j++) {
springCtx.lineTo(vertices[j].x, vertices[j].y);
}
springCtx.lineTo(vertices[0].x, vertices[0].y);
if (body.render.fillStyle) {
springCtx.fillStyle = body.render.fillStyle;
springCtx.fill();
}
if (body.render.strokeStyle) {
springCtx.strokeStyle = body.render.strokeStyle;
springCtx.lineWidth = body.render.lineWidth || 1;
springCtx.stroke();
}
}
}
// Render constraints (like the spring)
for (let i = 0; i < springWorld.constraints.length; i++) {
const constraint = springWorld.constraints[i];
if (constraint.render.visible) {
if (constraint.render.type === 'spring') {
renderSpringConstraint(constraint);
} else {
renderConstraint(constraint);
}
}
}
}
function renderSpringConstraint(constraint) {
const bodyA = constraint.pointA ? constraint.pointA : constraint.bodyA.position;
const bodyB = constraint.pointB ? constraint.pointB : constraint.bodyB.position;
const pointA = constraint.pointA || { x: 0, y: 0 };
const pointB = constraint.pointB || { x: 0, y: 0 };
const posA = bodyA.position ? {
x: bodyA.position.x + pointA.x,
y: bodyA.position.y + pointA.y
} : { x: bodyA.x, y: bodyA.y };
const posB = bodyB.position ? {
x: bodyB.position.x + pointB.x,
y: bodyB.position.y + pointB.y
} : { x: bodyB.x, y: bodyB.y };
const dx = posB.x - posA.x;
const dy = posB.y - posA.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const segments = Math.ceil(distance / 10);
springCtx.beginPath();
springCtx.moveTo(posA.x, posA.y);
for (let i = 1; i < segments; i++) {
const x = posA.x + (dx * i) / segments;
const y = posA.y + (dy * i) / segments + (i % 2 === 0 ? 5 : -5);
springCtx.lineTo(x, y);
}
springCtx.lineTo(posB.x, posB.y);
springCtx.strokeStyle = constraint.render.strokeStyle || '#7f8c8d';
springCtx.lineWidth = constraint.render.lineWidth || 1;
springCtx.stroke();
}
function renderConstraint(constraint) {
const bodyA = constraint.bodyA;
const pointA = constraint.pointA || { x: 0, y: 0 };
const posA = {
x: bodyA.position.x + pointA.x,
y: bodyA.position.y + pointA.y
};
const bodyB = constraint.bodyB;
const pointB = constraint.pointB || { x: 0, y: 0 };
const posB = {
x: bodyB.position.x + pointB.x,
y: bodyB.position.y + pointB.y
};
springCtx.beginPath();
springCtx.moveTo(posA.x, posA.y);
springCtx.lineTo(posB.x, posB.y);
springCtx.strokeStyle = constraint.render.strokeStyle || '#7f8c8d';
springCtx.lineWidth = constraint.render.lineWidth || 1;
springCtx.stroke();
}
function resetSpringPendulum() {
createSpringSystem();
Matter.Body.setPosition(springBody, { x: springCanvas.width / 2, y: 150 });
Matter.Body.setVelocity(springBody, { x: 3, y: 0 });
}
// Collision Lab Simulation
const collisionCanvas = document.getElementById('collisionCanvas');
const collisionEngine = Matter.Engine.create({
render: {
canvas: collisionCanvas,
options: {
width: collisionCanvas.width,
height: collisionCanvas.height,
wireframes: false,
background: '#ffffff'
}
}
});
const collisionWorld = collisionEngine.world;
let restitution = 0.7;
let friction = 0.01;
function updateCollisionValues() {
document.getElementById('restitutionValue').textContent = restitution;
document.getElementById('frictionValue').textContent = friction;
}
function createCollisionEnvironment() {
// Clear existing bodies
Matter.Composite.clear(collisionWorld);
// Create walls
const wallOptions = { isStatic: true, render: { fillStyle: '#34495e' } };
const ground = Matter.Bodies.rectangle(collisionCanvas.width / 2, collisionCanvas.height - 10, collisionCanvas.width, 20, wallOptions);
const leftWall = Matter.Bodies.rectangle(10, collisionCanvas.height / 2, 20, collisionCanvas.height, wallOptions);
const rightWall = Matter.Bodies.rectangle(collisionCanvas.width - 10, collisionCanvas.height / 2, 20, collisionCanvas.height, wallOptions);
const ceiling = Matter.Bodies.rectangle(collisionCanvas.width / 2, 10, collisionCanvas.width, 20, wallOptions);
Matter.Composite.add(collisionWorld, [ground, leftWall, rightWall, ceiling]);
// Create initial balls
addBall(collisionCanvas.width / 4, collisionCanvas.height / 2);
addBall((3 * collisionCanvas.width) / 4, collisionCanvas.height / 2, -5, 0);
}
function addBall(x = null, y = null, vx = null, vy = null) {
const radius = 15 + Math.random() * 10;
const xPos = x || radius + Math.random() * (collisionCanvas.width - 2 * radius);
const yPos = y || radius + Math.random() * (collisionCanvas.height / 2);
const xVel = vx || (Math.random() * 4 - 2);
const yVel = vy || (Math.random() * 2 - 1);
const ball = Matter.Bodies.circle(xPos, yPos, radius, {
restitution: restitution,
friction: friction,
render: {
fillStyle: `hsl(${Math.random() * 360}, 70%, 60%)`,
strokeStyle: '#34495e',
lineWidth: 1
}
});
Matter.Body.setVelocity(ball, { x: xVel, y: yVel });
Matter.Composite.add(collisionWorld, ball);
}
function resetCollisionLab() {
createCollisionEnvironment();
}
// Event Listeners
document.getElementById('velocity').addEventListener('input', function() {
velocity = parseFloat(this.value);
updateProjectileValues();
resetProjectile();
});
document.getElementById('angle').addEventListener('input', function() {
angle = parseFloat(this.value);
updateProjectileValues();
resetProjectile();
});
document.getElementById('gravity').addEventListener('input', function() {
gravity = parseFloat(this.value);
updateProjectileValues();
resetProjectile();
});
document.getElementById('resetProjectile').addEventListener('click', resetProjectile);
document.getElementById('springConstant').addEventListener('input', function() {
springConstant = parseFloat(this.value);
updateSpringValues();
resetSpringPendulum();
});
document.getElementById('mass').addEventListener('input', function() {
mass = parseFloat(this.value);
updateSpringValues();
resetSpringPendulum();
});
document.getElementById('damping').addEventListener('input', function() {
damping = parseFloat(this.value);
updateSpringValues();
resetSpringPendulum();
});
document.getElementById('resetSpring').addEventListener('click', resetSpringPendulum);
document.getElementById('restitution').addEventListener('input', function() {
restitution = parseFloat(this.value);
updateCollisionValues();
});
document.getElementById('friction').addEventListener('input', function() {
friction = parseFloat(this.value);
updateCollisionValues();
});
document.getElementById('addBall').addEventListener('click', addBall);
document.getElementById('resetCollision').addEventListener('click', resetCollisionLab);
// Initialize simulations
updateProjectileValues();
updateSpringValues();
updateCollisionValues();
resetProjectile();
resetSpringPendulum();
resetCollisionLab();
// Game loop for spring simulation
function gameLoop() {
Matter.Engine.update(springEngine, 1000 / 60);
renderSpring();
Matter.Engine.update(collisionEngine);
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>
</body>
</html>
```
## Features of this Physics Playground:
1. **Three Interactive Simulations**:
- Projectile Motion: Control launch angle, velocity, and gravity
- Spring Pendulum: Adjust spring constant, mass, and damping
- Collision Lab: Modify elasticity and friction of colliding balls
2. **Real-time Adjustments**:
- All sliders update the simulations immediately
- Visual feedback showing parameter values
3. **Interactive Elements**:
- Reset buttons for each simulation
- "Add Ball" button in the collision lab
4. **Visual Feedback**:
- Clear displays of parameter values
- Color-coded elements for better visualization
The simulations use both custom canvas drawing and Matter.js physics engine for realistic physics behavior. Users can explore how changing parameters affects the physical systems in real-time.```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Physics Playground</title>
<style>
body {
margin: 0;
padding: 20px;
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
overflow-x: hidden;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.physics-demo {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 20px;
margin-bottom: 30px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.demo-title {
font-size: 1.5em;
margin-bottom: 15px;
text-align: center;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
justify-content: center;
}
.control-group {
display: flex;
flex-direction: column;
align-items: center;
min-width: 150px;
}
.control-group label {
margin-bottom: 5px;
font-weight: bold;
}
.slider {
width: 150px;
height: 8px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.3);
outline: none;
margin-bottom: 5px;
}
.slider::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #4CAF50;
cursor: pointer;
border: 2px solid white;
}
.value-display {
font-size: 0.9em;
color: #FFD700;
}
.canvas-container {
background: rgba(0, 0, 0, 0.2);
border-radius: 10px;
padding: 10px;
display: flex;
justify-content: center;
}
canvas {
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
background: #000;
}
.info-panel {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 15px;
margin-top: 15px;
text-align: center;
}
.ball {
border-radius: 50%;
position: absolute;
}
.reset-btn {
background: #FF6B6B;
border: none;
color: white;
padding: 10px 20px;
border-radius: 25px;
cursor: pointer;
font-size: 1em;
transition: background 0.3s;
}
.reset-btn:hover {
background: #FF5252;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Physics Playground</h1>
<!-- Projectile Motion Demo -->
<div class="physics-demo">
<h2 class="demo-title">🎯 Projectile Motion</h2>
<div class="controls">
<div class="control-group">
<label for="angle">Launch Angle</label>
<input type="range" class="slider" id="angle" min="1" max="89" value="45">
<span class="value-display" id="angleValue">45°</span>
</div>
<div class="control-group">
<label for="velocity">Initial Velocity</label>
<input type="range" class="slider" id="velocity" min="10" max="100" value="50">
<span class="value-display" id="velocityValue">50 m/s</span>
</div>
<div class="control-group">
<label for="gravity">Gravity</label>
<input type="range" class="slider" id="gravity" min="1" max="20" value="9.8" step="0.1">
<span class="value-display" id="gravityValue">9.8 m/s²</span>
</div>
<div class="control-group">
<button class="reset-btn" onclick="resetProjectile()">Reset</button>
</div>
</div>
<div class="canvas-container">
<canvas id="projectileCanvas" width="800" height="400"></canvas>
</div>
<div class="info-panel">
<div id="projectileInfo">Range: 0m | Max Height: 0m | Flight Time: 0s</div>
</div>
</div>
<!-- Pendulum Demo -->
<div class="physics-demo">
<h2 class="demo-title">⌚ Simple Pendulum</h2>
<div class="controls">
<div class="control-group">
<label for="pendulumLength">Length</label>
<input type="range" class="slider" id="pendulumLength" min="50" max="200" value="100">
<span class="value-display" id="lengthValue">100 cm</span>
</div>
<div class="control-group">
<label for="pendulumAngle">Initial Angle</label>
<input type="range" class="slider" id="pendulumAngle" min="10" max="60" value="30">
<span class="value-display" id="pendulumAngleValue">30°</span>
</div>
<div class="control-group">
<label for="pendulumGravity">Gravity</label>
<input type="range" class="slider" id="pendulumGravity" min="1" max="20" value="9.8" step="0.1">
<span class="value-display" id="pendulumGravityValue">9.8 m/s²</span>
</div>
<div class="control-group">
<button class="reset-btn" onclick="resetPendulum()">Reset</button>
</div>
</div>
<div class="canvas-container">
<canvas id="pendulumCanvas" width="400" height="300"></canvas>
</div>
<div class="info-panel">
<div id="pendulumInfo">Period: 0s | Frequency: 0 Hz</div>
</div>
</div>
<!-- Wave Demo -->
<div class="physics-demo">
<h2 class="demo-title">🌊 Wave Motion</h2>
<div class="controls">
<div class="control-group">
<label for="waveAmplitude">Amplitude</label>
<input type="range" class="slider" id="waveAmplitude" min="10" max="80" value="40">
<span class="value-display" id="amplitudeValue">40 px</span>
</div>
<div class="control-group">
<label for="waveFrequency">Frequency</label>
<input type="range" class="slider" id="waveFrequency" min="0.1" max="3" value="1" step="0.1">
<span class="value-display" id="frequencyValue">1.0 Hz</span>
</div>
<div class="control-group">
<label for="waveSpeed">Wave Speed</label>
<input type="range" class="slider" id="waveSpeed" min="1" max="10" value="5">
<span class="value-display" id="speedValue">5</span>
</div>
<div class="control-group">
<button class="reset-btn" onclick="resetWave()">Reset</button>
</div>
</div>
<div class="canvas-container">
<canvas id="waveCanvas" width="800" height="200"></canvas>
</div>
</div>
</div>
<script>
// Projectile Motion
let projectileCanvas = document.getElementById('projectileCanvas');
let projectileCtx = projectileCanvas.getContext('2d');
let projectileAnimation;
let projectilePath = [];
function drawProjectile() {
let angle = document.getElementById('angle').value * Math.PI / 180;
let velocity = parseFloat(document.getElementById('velocity').value);
let gravity = parseFloat(document.getElementById('gravity').value);
document.getElementById('angleValue').textContent = document.getElementById('angle').value + '°';
document.getElementById('velocityValue').textContent = velocity + ' m/s';
document.getElementById('gravityValue').textContent = gravity + ' m/s²';
let vx = velocity * Math.cos(angle);
let vy = velocity * Math.sin(angle);
let range = (velocity * velocity * Math.sin(2 * angle)) / gravity;
let maxHeight = (velocity * velocity * Math.sin(angle) * Math.sin(angle)) / (2 * gravity);
let flightTime = (2 * velocity * Math.sin(angle)) / gravity;
document.getElementById('projectileInfo').innerHTML =
`Range: ${range.toFixed(1)}m | Max Height: ${maxHeight.toFixed(1)}m | Flight Time: ${flightTime.toFixed(1)}s`;
projectileCtx.clearRect(0, 0, projectileCanvas.width, projectileCanvas.height);
// Draw ground
projectileCtx.strokeStyle = '#4CAF50';
projectileCtx.lineWidth = 3;
projectileCtx.beginPath();
projectileCtx.moveTo(0, projectileCanvas.height - 20);
projectileCtx.lineTo(projectileCanvas.width, projectileCanvas.height - 20);
projectileCtx.stroke();
// Draw trajectory
projectileCtx.strokeStyle = '#FFD700';
projectileCtx.lineWidth = 2;
projectileCtx.beginPath();
let scale = 5; // Scale factor for visualization
for (let t = 0; t <= flightTime; t += 0.1) {
let x = vx * t * scale;
let y = projectileCanvas.height - 20 - (vy * t - 0.5 * gravity * t * t) * scale;
if (x > projectileCanvas.width) break;
if (y > projectileCanvas.height - 20) break;
if (t === 0) {
projectileCtx.moveTo(x, y);
} else {
projectileCtx.lineTo(x, y);
}
}
projectileCtx.stroke();
}
function resetProjectile() {
document.getElementById('angle').value = 45;
document.getElementById('velocity').value = 50;
document.getElementById('gravity').value = 9.8;
drawProjectile();
}
// Pendulum Motion
let pendulumCanvas = document.getElementById('pendulumCanvas');
let pendulumCtx = pendulumCanvas.getContext('2d');
let pendulumTime = 0;
let pendulumAnimation;
function drawPendulum() {
let length = parseFloat(document.getElementById('pendulumLength').value);
let initialAngle = parseFloat(document.getElementById('pendulumAngle').value) * Math.PI / 180;
let gravity = parseFloat(document.getElementById('pendulumGravity').value);
document.getElementById('lengthValue').textContent = length + ' cm';
document.getElementById('pendulumAngleValue').textContent = document.getElementById('pendulumAngle').value + '°';
document.getElementById('pendulumGravityValue').textContent = gravity + ' m/s²';
let period = 2 * Math.PI * Math.sqrt(length / 100 / gravity);
let frequency = 1 / period;
document.getElementById('pendulumInfo').innerHTML =
`Period: ${period.toFixed(2)}s | Frequency: ${frequency.toFixed(2)} Hz`;
let centerX = pendulumCanvas.width / 2;
let centerY = 50;
let angle = initialAngle * Math.cos(Math.sqrt(gravity / (length / 100)) * pendulumTime);
let bobX = centerX + length * Math.sin(angle);
let bobY = centerY + length * Math.cos(angle);
pendulumCtx.clearRect(0, 0, pendulumCanvas.width, pendulumCanvas.height);
// Draw pivot
pendulumCtx.fillStyle = '#FF6B6B';
pendulumCtx.beginPath();
pendulumCtx.arc(centerX, centerY, 8, 0, 2 * Math.PI);
pendulumCtx.fill();
// Draw string
pendulumCtx.strokeStyle = '#FFF';
pendulumCtx.lineWidth = 2;
pendulumCtx.beginPath();
pendulumCtx.moveTo(centerX, centerY);
pendulumCtx.lineTo(bobX, bobY);
pendulumCtx.stroke();
// Draw bob
pendulumCtx.fillStyle = '#4CAF50';
pendulumCtx.beginPath();
pendulumCtx.arc(bobX, bobY, 15, 0, 2 * Math.PI);
pendulumCtx.fill();
pendulumTime += 0.016; // 60fps
}
function resetPendulum() {
pendulumTime = 0;
document.getElementById('pendulumLength').value = 100;
document.getElementById('pendulumAngle').value = 30;
document.getElementById('pendulumGravity').value = 9.8;
}
// Wave Motion
let waveCanvas = document.getElementById('waveCanvas');
let waveCtx = waveCanvas.getContext('2d');
let waveTime = 0;
function drawWave() {
let amplitude = parseFloat(document.getElementById('waveAmplitude').value);
let frequency = parseFloat(document.getElementById('waveFrequency').value);
let speed = parseFloat(document.getElementById('waveSpeed').value);
document.getElementById('amplitudeValue').textContent = amplitude + ' px';
document.getElementById('frequencyValue').textContent = frequency.toFixed(1) + ' Hz';
document.getElementById('speedValue').textContent = speed;
waveCtx.clearRect(0, 0, waveCanvas.width, waveCanvas.height);
// Draw center line
waveCtx.strokeStyle = '#666';
waveCtx.lineWidth = 1;
waveCtx.beginPath();
waveCtx.moveTo(0, waveCanvas.height / 2);
waveCtx.lineTo(waveCanvas.width, waveCanvas.height / 2);
waveCtx.stroke();
// Draw wave
waveCtx.strokeStyle = '#00BFFF';
waveCtx.lineWidth = 3;
waveCtx.beginPath();
for (let x = 0; x < waveCanvas.width; x += 2) {
let y = waveCanvas.height / 2 + amplitude * Math.sin((x * 0.02 * frequency) + (waveTime * speed));
if (x === 0) {
waveCtx.moveTo(x, y);
} else {
waveCtx.lineTo(x, y);
}
}
waveCtx.stroke();
waveTime += 0.05;
}
function resetWave() {
waveTime = 0;
document.getElementById('waveAmplitude').value = 40;
document.getElementById('waveFrequency').value = 1;
document.getElementById('waveSpeed').value = 5;
}
// Event listeners
document.getElementById('angle').addEventListener('input', drawProjectile);
document.getElementById('velocity').addEventListener('input', drawProjectile);
document.getElementById('gravity').addEventListener('input', drawProjectile);
document.getElementById('pendulumLength').addEventListener('input', resetPendulum);
document.getElementById('pendulumAngle').addEventListener('input', resetPendulum);
document.getElementById('pendulumGravity').addEventListener('input', resetPendulum);
document.getElementById('waveAmplitude').addEventListener('input', resetWave);
document.getElementById('waveFrequency').addEventListener('input', resetWave);
document.getElementById('waveSpeed').addEventListener('input', resetWave);
// Animation loops
function animate() {
drawProjectile();
drawPendulum();
drawWave();
requestAnimationFrame(animate);
}
// Initialize
drawProjectile();
resetPendulum();
resetWave();
animate();
</script>
</body>
</html>
```
This physics playground features three interactive demonstrations:
🎯 **Projectile Motion**: Adjust launch angle, initial velocity, and gravity to see how they affect the trajectory, range, maximum height, and flight time.
⌚ **Simple Pendulum**: Modify the pendulum length, initial angle, and gravity to observe how these parameters influence the period and frequency of oscillation.
🌊 **Wave Motion**: Control wave amplitude, frequency, and speed to visualize how these properties affect wave behavior.
Each demo includes:
- Interactive sliders with real-time value displays
- Live animated visualizations
- Calculated physics values (range, period, frequency, etc.)
- Reset buttons to return to default settings
- Smooth animations running at 60fps
The interface is responsive and uses a modern glassmorphism design with a beautiful gradient background. All physics calculations are mathematically accurate and update in real-time as you adjust the parameters!```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Physics Playground</title>
<!-- Matter-JS Physics Engine -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"
integrity="sha512-km0td6CB2vBDqXZkyOim8wzntrO4XQeSVQH8yWg6gCYCgU+uLTWCHH3qMoaEFE2c+I6P87kpclYbcXpy9R4yMA=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<style>
:root{
--accent:#006aff;
--bg:#fafafa;
--panel:#ffffff;
--text:#222;
--border:#d0d0d0;
}
*{box-sizing:border-box;font-family:Arial,Helvetica,sans-serif;}
body{
margin:0;
display:flex;
flex-direction:column;
height:100vh;
background:var(--bg);
color:var(--text);
}
#controls{
padding:10px 18px;
background:var(--panel);
border-bottom:1px solid var(--border);
display:flex;
align-items:center;
column-gap:22px;
flex-wrap:wrap;
}
#controls label{
font-size:14px;
display:flex;
align-items:center;
gap:6px;
}
#controls input[type=range]{
accent-color:var(--accent);
}
#world{
flex:1;
display:block; /* Remove small gap under canvas */
width:100%;
height:100%;
}
button{
padding:6px 12px;
border:1px solid var(--border);
border-radius:4px;
background:var(--accent);
color:#fff;
cursor:pointer;
}
button:hover{opacity:.9;}
output{min-width:40px;display:inline-block;text-align:right;}
@media (max-width:600px){
#controls label{width:100%;}
}
</style>
</head>
<body>
<!-- Control Panel -->
<div id="controls">
<label>Gravity
<input id="gravSlider" type="range" min="0" max="2" step="0.01" value="1">
<output id="gravOut">1.00</output>
</label>
<label>Restitution
<input id="restSlider" type="range" min="0" max="1" step="0.01" value="0.7">
<output id="restOut">0.70</output>
</label>
<label>Friction
<input id="fricSlider" type="range" min="0" max="0.2" step="0.005" value="0.05">
<output id="fricOut">0.05</output>
</label>
<button id="addBtn">Add Ball</button>
<button id="clearBtn">Clear</button>
</div>
<!-- Physics Canvas -->
<canvas id="world"></canvas>
<script>
/* ------ INITIALISE MATTER-JS ------ */
const {Engine,Render,Runner,Bodies,Composite,Events,Body} = Matter;
const engine = Engine.create();
const world = engine.world;
world.gravity.scale = 0.001; // keep numbers human-friendly
/* Canvas sizing */
const canvas = document.getElementById('world');
const render = Render.create({
canvas:canvas,
engine:engine,
options:{
width: window.innerWidth,
height: window.innerHeight - document.getElementById('controls').offsetHeight,
background:'#f5f5f5',
wireframes:false,
pixelRatio: window.devicePixelRatio
}
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner,engine);
/* Walls & Ground */
function makeWalls(){
const w = render.options.width,
h = render.options.height;
const thickness = 40;
const ground = Bodies.rectangle(w/2, h+thickness/2, w, thickness, {isStatic:true});
const ceil = Bodies.rectangle(w/2,-thickness/2,w,thickness,{isStatic:true});
const left = Bodies.rectangle(-thickness/2, h/2, thickness, h, {isStatic:true});
const right = Bodies.rectangle(w+thickness/2, h/2, thickness, h, {isStatic:true});
Composite.add(world,[ground,ceil,left,right]);
}
makeWalls();
/* ------ UI HANDLERS ------ */
const gSlider = document.getElementById('gravSlider');
const rSlider = document.getElementById('restSlider');
const fSlider = document.getElementById('fricSlider');
const gOut = document.getElementById('gravOut');
const rOut = document.getElementById('restOut');
const fOut = document.getElementById('fricOut');
function updateOutputs(){
gOut.value = parseFloat(gSlider.value).toFixed(2);
rOut.value = parseFloat(rSlider.value).toFixed(2);
fOut.value = parseFloat(fSlider.value).toFixed(2);
}
updateOutputs();
/* Update world gravity on slider move */
gSlider.addEventListener('input',() => {
world.gravity.y = parseFloat(gSlider.value);
updateOutputs();
});
/* Restitution / friction : update all dynamic bodies */
function updateMaterial(){
const rest = parseFloat(rSlider.value);
const fric = parseFloat(fSlider.value);
Composite.allBodies(world).forEach(b=>{
if(!b.isStatic){
b.restitution = rest;
b.friction = fric;
}
});
updateOutputs();
}
rSlider.addEventListener('input',updateMaterial);
fSlider.addEventListener('input',updateMaterial);
/* ------ SPAWN & UTILS ------ */
function addBall(x = render.options.width*0.5 , y = 0){
const radius = 20 + Math.random()*25;
const ball = Bodies.circle(x,y,radius,{
restitution: parseFloat(rSlider.value),
friction: parseFloat(fSlider.value),
density:0.002
});
Composite.add(world,ball);
}
/* Button hooks */
document.getElementById('addBtn').addEventListener('click',()=>addBall(Math.random()*render.options.width, -20));
document.getElementById('clearBtn').addEventListener('click',()=>{
/* remove all but static bodies */
Composite.allBodies(world).forEach(b=>{
if(!b.isStatic) Composite.remove(world,b);
});
});
/* Resize canvas & walls on window resize */
window.addEventListener('resize',()=>{
render.options.width = canvas.width = window.innerWidth;
render.options.height = canvas.height = window.innerHeight - document.getElementById('controls').offsetHeight;
/* Rebuild walls */
Composite.allBodies(world).forEach(b=>{ if(b.isStatic) Composite.remove(world,b);});
makeWalls();
});
/* Simple colour styling */
Events.on(render,'afterRender',()=>{
const bodies = Composite.allBodies(world);
const ctx = render.context;
ctx.lineWidth = 1;
bodies.forEach(body=>{
if(body.isStatic) return; // leave walls unpainted
ctx.beginPath();
body.parts.forEach((part,idx)=>{
const vertices = part.vertices;
ctx.moveTo(vertices[0].x,vertices[0].y);
for(let j=1;j<vertices.length;j++){
ctx.lineTo(vertices[j].x,vertices[j].y);
}
ctx.lineTo(vertices[0].x,vertices[0].y);
});
ctx.fillStyle = '#'+(Math.random()*0xffffff<<0).toString(16).padStart(6,'0');
ctx.fill();
ctx.strokeStyle='#333';ctx.stroke();
});
});
</script>
</body>
</html>
```Okay, here's a complete HTML page demonstrating a simple physics simulation using JavaScript and HTML5 Canvas. It includes interactive sliders that affect the properties of a "ball" object, allowing the user to observe the changes in real-time. The code incorporates all necessary HTML and JavaScript tags within a single code block.
```html
<!DOCTYPE html>
<html>
<head>
<title>Simple Physics Playground</title>
<style>
body {
font-family: sans-serif;
text-align: center;
}
#canvas-container {
display: flex;
justify-content: center;
align-items: center;
height: 400px; /* Adjust as needed */
}
</style>
</head>
<body>
<h1>Simple Physics Playground</h1>
<div id="canvas-container">
<canvas id="physicsCanvas" width="600" height="400"></canvas>
</div>
<p>
<b>Gravity:</b> <input type="range" min="0.1" max="2.0" value="1.0" id="gravitySlider"> <span id="gravityValue">1.0</span>
</p>
<p>
<b>Friction:</b> <input type="range" min="0.0" max="0.5" value="0.2" id="frictionSlider"> <span id="frictionValue">0.2</span>
</p>
<p>
<b>Ball Mass:</b> <input type="range" min="0.1" max="2.0" value="1.0" id="massSlider"> <span id="massValue">1.0</span>
</p>
<script>
const canvas = document.getElementById('physicsCanvas');
const ctx = canvas.getContext('2d');
let ballX = canvas.width / 2;
let ballY = canvas.height / 2;
let ballRadius = 20;
let velocityX = 0;
let velocityY = 0;
let mass = 1.0;
// Slider event handlers
document.getElementById('gravitySlider').addEventListener('input', function() {
document.getElementById('gravityValue').innerText = this.value;
gravity = parseFloat(this.value);
});
document.getElementById('frictionSlider').addEventListener('input', function() {
document.getElementById('frictionValue').innerText = this.value;
friction = parseFloat(this.value);
});
document.getElementById('massSlider').addEventListener('input', function() {
document.getElementById('massValue').innerText = this.value;
mass = parseFloat(this.value);
});
// Physics update function
function update() {
// Apply gravity
velocityY += gravity;
// Apply friction
velocityX *= (1 - friction);
velocityY *= (1 - friction);
// Update position
ballX += velocityX;
ballY += velocityY;
// Bounce off walls
if (ballX + ballRadius > canvas.width || ballX - ballRadius < 0) {
velocityX = -velocityX;
}
if (ballY + ballRadius > canvas.height || ballY - ballRadius < 0) {
velocityY = -velocityY;
}
// Ball mass impact - Basic example - affects velocity change
velocityX *= (1 - (mass / 10));
velocityY *= (1 - (mass / 10));
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw ball
ctx.beginPath();
ctx.arc(ballX, ballY, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
}
// Game loop
function gameLoop() {
update();
ctx.beginPath();
ctx.arc(ballX, ballY, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
requestAnimationFrame(gameLoop);
}
// Start the game loop
gameLoop();
</script>
</body>
</html>
```
Key improvements and explanations:
* **Clearer HTML Structure:** Uses a `div` with `id="canvas-container"` to center the canvas horizontally. The sliders are placed below with labels and display values.
* **CSS Styling:** Added CSS to center the canvas and provide a basic appearance.
* **JavaScript Clarity:** More descriptive variable names and comments.
* **Slider Integration:** Event listeners are attached to each slider. The slider's `input` event updates the corresponding `<span>` element to show the current value. The slider values are directly used in the physics calculations.
* **Physics Calculations:** The `update()` function contains the core physics logic: gravity, friction, position updates, and wall bouncing. Crucially, it now includes the effect of the 'mass' slider. The velocity change is scaled by (1 - mass / 10) This can be experimented with.
* **Game Loop:** The `gameLoop()` function uses `requestAnimationFrame()` for smooth and efficient animation.
* **Canvas Drawing:** The `gameLoop()` function redraws the ball on each frame using `ctx.arc()`.
* **Complete Code:** Everything is contained within a single, runnable HTML file.
How to Run:
1. Save the code as an HTML file (e.g., `physics_playground.html`).
2. Open the HTML file in any web browser.
Experiment with the sliders to see how they affect the ball's motion. The mass slider is particularly interesting – it changes the *speed* at which the ball slows down due to friction.
This example provides a foundation. You can expand upon it by adding more features, such as:
* Different shapes
* Multiple balls
* Collision detection
* More complex forces (e.g., spring forces)
* A more sophisticated rendering system```html
<!DOCTYPE html>
<html>
<head>
<title>Physics Playground</title>
<style>
body {
font-family: sans-serif;
}
#container {
display: flex;
flex-direction: column;
align-items: center;
}
#canvas-container {
width: 800px;
height: 600px;
border: 1px solid black;
margin-bottom: 20px;
position: relative; /* Needed for absolute positioning of elements within */
}
#controls {
display: flex;
flex-direction: row;
justify-content: space-around;
width: 800px;
}
.slider-container {
display: flex;
flex-direction: column;
align-items: center;
}
input[type="range"] {
width: 200px;
}
#info {
text-align: center;
margin-top: 10px;
}
</style>
</head>
<body>
<div id="container">
<div id="canvas-container">
<canvas id="physicsCanvas" width="800" height="600"></canvas>
</div>
<div id="controls">
<div class="slider-container">
<label for="gravity">Gravity:</label>
<input type="range" id="gravity" min="0" max="2" step="0.01" value="1">
<span id="gravity-value">1</span>
</div>
<div class="slider-container">
<label for="damping">Damping:</label>
<input type="range" id="damping" min="0" max="0.99" step="0.01" value="0.95">
<span id="damping-value">0.95</span>
</div>
<div class="slider-container">
<label for="bounciness">Bounciness:</label>
<input type="range" id="bounciness" min="0" max="1" step="0.01" value="0.8">
<span id="bounciness-value">0.8</span>
</div>
</div>
<div id="info">
Click and drag to create and launch particles.
Scroll to adjust particle size.
</div>
</div>
<script>
const canvas = document.getElementById('physicsCanvas');
const ctx = canvas.getContext('2d');
const gravitySlider = document.getElementById('gravity');
const dampingSlider = document.getElementById('damping');
const bouncinessSlider = document.getElementById('bounciness');
const gravityValueDisplay = document.getElementById('gravity-value');
const dampingValueDisplay = document.getElementById('damping-value');
const bouncinessValueDisplay = document.getElementById('bounciness-value');
let gravity = parseFloat(gravitySlider.value);
let damping = parseFloat(dampingSlider.value);
let bounciness = parseFloat(bouncinessSlider.value);
let particles = [];
let isDragging = false;
let currentParticle = null;
// Update display values
gravityValueDisplay.textContent = gravity.toFixed(2);
dampingValueDisplay.textContent = damping.toFixed(2);
bouncinessValueDisplay.textContent = bounciness.toFixed(2);
//Event Listeners
gravitySlider.addEventListener('input', () => {
gravity = parseFloat(gravitySlider.value);
gravityValueDisplay.textContent = gravity.toFixed(2);
});
dampingSlider.addEventListener('input', () => {
damping = parseFloat(dampingSlider.value);
dampingValueDisplay.textContent = damping.toFixed(2);
});
bouncinessSlider.addEventListener('input', () => {
bounciness = parseFloat(bouncinessSlider.value);
bouncinessValueDisplay.textContent = bounciness.toFixed(2);
});
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
currentParticle = {
x: e.clientX - canvas.offsetLeft,
y: e.clientY - canvas.offsetTop,
vx: 0,
vy: 0,
radius: 5 + (e.deltaY * -0.3) //Size based on scroll
};
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
if (currentParticle) {
particles.push(currentParticle);
currentParticle = null;
}
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging && currentParticle) {
currentParticle.x = e.clientX - canvas.offsetLeft;
currentParticle.y = e.clientY - canvas.offsetTop;
}
});
// Particle class
function Particle(x, y, vx, vy, radius) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.radius = radius;
}
// Update function
function update() {
for (let i = 0; i < particles.length; i++) {
let particle = particles[i];
particle.vy += gravity;
particle.vx *= damping;
particle.vy *= damping;
particle.x += particle.vx;
particle.y += particle.vy;
// Bounce off walls
if (particle.x + particle.radius > canvas.width || particle.x - particle.radius < 0) {
particle.vx *= -bounciness;
}
if (particle.y + particle.radius > canvas.height || particle.y - particle.radius < 0) {
particle.vy *= -bounciness;
}
}
}
// Draw function
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < particles.length; i++) {
let particle = particles[i];
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
}
}
// Animation loop
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
```Below is an example HTML file that creates a simple "Physics Playground" webpage. This example will simulate a mass-spring system where you can adjust the mass, spring constant, and damping using sliders. The simulation will update in real-time as you adjust these parameters.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Physics Playground</title>
<style>
body {
font-family: Arial, sans-serif;
}
#canvas {
border: 1px solid black;
}
.controls {
margin-top: 10px;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<div class="controls">
<label for="mass">Mass:</label>
<input type="range" id="mass" min="1" max="100" value="10">
<span id="massValue">10</span>
<br>
<label for="springConstant">Spring Constant:</label>
<input type="range" id="springConstant" min="0.1" max="10" value="1" step="0.1">
<span id="springConstantValue">1</span>
<br>
<label for="damping">Damping:</label>
<input type="range" id="damping" min="0" max="5" value="0.5" step="0.1">
<span id="dampingValue">0.5</span>
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const massSlider = document.getElementById('mass');
const springConstantSlider = document.getElementById('springConstant');
const dampingSlider = document.getElementById('damping');
const massValueSpan = document.getElementById('massValue');
const springConstantValueSpan = document.getElementById('springConstantValue');
const dampingValueSpan = document.getElementById('dampingValue');
let mass = parseFloat(massSlider.value);
let springConstant = parseFloat(springConstantSlider.value);
let damping = parseFloat(dampingSlider.value);
let displacement = 100;
let velocity = 0;
let equilibrium = canvas.height / 2;
massSlider.oninput = () => {
mass = parseFloat(massSlider.value);
massValueSpan.textContent = mass;
};
springConstantSlider.oninput = () => {
springConstant = parseFloat(springConstantSlider.value);
springConstantValueSpan.textContent = springConstant;
};
dampingSlider.oninput = () => {
damping = parseFloat(dampingSlider.value);
dampingValueSpan.textContent = damping;
};
function simulate() {
// Simple Euler method for integration
let acceleration = (-springConstant * displacement - damping * velocity) / mass;
velocity += acceleration * 0.02; // Assuming dt = 0.02 seconds
displacement += velocity * 0.02;
// Draw
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(canvas.width / 2, equilibrium);
ctx.lineTo(canvas.width / 2, equilibrium + displacement);
ctx.stroke();
ctx.beginPath();
ctx.arc(canvas.width / 2, equilibrium + displacement, mass / 2, 0, 2 * Math.PI);
ctx.fillStyle = 'blue';
ctx.fill();
// Spring representation
ctx.beginPath();
ctx.moveTo(canvas.width / 2, equilibrium);
let y = equilibrium;
let dy = (equilibrium + displacement - y) / 10;
for (let i = 0; i < 10; i++) {
ctx.lineTo(canvas.width / 2 + (i % 2 === 0 ? 10 : -10), y + dy * i);
}
ctx.lineTo(canvas.width / 2, equilibrium + displacement);
ctx.stroke();
requestAnimationFrame(simulate);
}
simulate();
</script>
</body>
</html>
```
This example creates a simple mass-spring-damper system simulation. You can adjust the mass, spring constant, and damping using the sliders, and see how these changes affect the motion of the mass in real-time.
To use this code, simply copy and paste it into an HTML file and open it in a web browser. You don't need any external libraries or setup beyond a modern web browser.