
Educational Web
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> ```
Drag to resize
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
Drag to resize
```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> ```
Drag to resize
```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> ```
Drag to resize
```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> ```
Drag to resize
# 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.
Drag to resize
```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!
Drag to resize
```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> ```
Drag to resize
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
Drag to resize
```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> ```
Drag to resize
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.
Drag to resize