virtual-spring / index.html
amirpoorazima's picture
Add 3 files
a6c7a66 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Virtual Spring Simulation</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
<style>
.spring-container {
position: relative;
height: 400px;
overflow: hidden;
}
.spring {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 8px;
background-color: #3b82f6;
border-radius: 4px;
transition: height 0.1s ease-out;
}
.mass {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 80px;
background-color: #10b981;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.controls {
background-color: #f3f4f6;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.slider-container {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.slider {
flex-grow: 1;
-webkit-appearance: none;
height: 8px;
border-radius: 4px;
background: #d1d5db;
outline: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
.spring-anchor {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 100px;
height: 20px;
background-color: #6b7280;
border-radius: 0 0 10px 10px;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-indigo-700 mb-2">Virtual Spring Simulator</h1>
<p class="text-gray-600">Explore Hooke's Law with this interactive spring-mass system</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div class="lg:col-span-2">
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
<div class="spring-container relative">
<div class="spring-anchor"></div>
<div id="spring" class="spring bg-blue-500"></div>
<div id="mass" class="mass bg-emerald-500">1 kg</div>
</div>
<div class="p-4 bg-gray-50 border-t border-gray-200">
<button id="startBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg mr-3 transition">
<i class="fas fa-play mr-2"></i> Start Oscillation
</button>
<button id="stopBtn" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition">
<i class="fas fa-stop mr-2"></i> Stop
</button>
<button id="resetBtn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg float-right transition">
<i class="fas fa-undo mr-2"></i> Reset
</button>
</div>
</div>
</div>
<div class="controls">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Spring Parameters</h2>
<div class="slider-container">
<span class="text-gray-700 w-24">Mass (kg)</span>
<input type="range" id="massSlider" class="slider" min="0.1" max="5" step="0.1" value="1">
<span id="massValue" class="text-gray-700 w-12">1.0</span>
</div>
<div class="slider-container">
<span class="text-gray-700 w-24">Spring Constant (N/m)</span>
<input type="range" id="kSlider" class="slider" min="1" max="50" step="1" value="10">
<span id="kValue" class="text-gray-700 w-12">10</span>
</div>
<div class="slider-container">
<span class="text-gray-700 w-24">Damping</span>
<input type="range" id="dampingSlider" class="slider" min="0" max="0.5" step="0.01" value="0.05">
<span id="dampingValue" class="text-gray-700 w-12">0.05</span>
</div>
<div class="slider-container">
<span class="text-gray-700 w-24">Initial Displacement (cm)</span>
<input type="range" id="displacementSlider" class="slider" min="0" max="50" step="1" value="20">
<span id="displacementValue" class="text-gray-700 w-12">20</span>
</div>
<div class="mt-6 p-4 bg-indigo-50 rounded-lg">
<h3 class="font-medium text-indigo-800 mb-2">Physics Information</h3>
<p class="text-sm text-gray-700 mb-1"><span class="font-medium">Natural Frequency:</span> <span id="frequency">1.58</span> Hz</p>
<p class="text-sm text-gray-700 mb-1"><span class="font-medium">Period:</span> <span id="period">3.97</span> s</p>
<p class="text-sm text-gray-700"><span class="font-medium">Current Position:</span> <span id="position">20.0</span> cm</p>
</div>
</div>
</div>
<div class="mt-8 bg-white rounded-xl shadow-lg p-6">
<h2 class="text-2xl font-bold text-gray-800 mb-4">About Spring Systems</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-lg font-semibold text-indigo-700 mb-2">Hooke's Law</h3>
<p class="text-gray-600 mb-4">Hooke's Law states that the force exerted by a spring is proportional to its displacement from equilibrium: F = -kx, where k is the spring constant and x is the displacement.</p>
<div class="bg-gray-100 p-3 rounded-lg text-center">
<p class="font-mono text-gray-800">F = -k × x</p>
</div>
</div>
<div>
<h3 class="text-lg font-semibold text-indigo-700 mb-2">Simple Harmonic Motion</h3>
<p class="text-gray-600 mb-4">When a mass is attached to a spring, it exhibits simple harmonic motion. The period T of oscillation depends on the mass m and spring constant k:</p>
<div class="bg-gray-100 p-3 rounded-lg text-center">
<p class="font-mono text-gray-800">T = 2π√(m/k)</p>
</div>
</div>
</div>
</div>
</div>
<script>
// DOM elements
const spring = document.getElementById('spring');
const mass = document.getElementById('mass');
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const resetBtn = document.getElementById('resetBtn');
// Sliders and values
const massSlider = document.getElementById('massSlider');
const kSlider = document.getElementById('kSlider');
const dampingSlider = document.getElementById('dampingSlider');
const displacementSlider = document.getElementById('displacementSlider');
const massValue = document.getElementById('massValue');
const kValue = document.getElementById('kValue');
const dampingValue = document.getElementById('dampingValue');
const displacementValue = document.getElementById('displacementValue');
// Physics info
const frequencyEl = document.getElementById('frequency');
const periodEl = document.getElementById('period');
const positionEl = document.getElementById('position');
// Simulation variables
let animationId;
let isOscillating = false;
let equilibriumY = 150; // Equilibrium position in pixels
let currentY = equilibriumY;
let velocity = 0;
let lastTime = 0;
let initialDisplacement = 20; // cm
// Constants
const pixelsPerCm = 3; // Conversion factor
const g = 9.81; // gravity in m/s²
// Initialize the simulation
function init() {
updateParameters();
updatePhysicsInfo();
render();
}
// Update parameters from sliders
function updateParameters() {
const mass = parseFloat(massSlider.value);
const k = parseFloat(kSlider.value);
const damping = parseFloat(dampingSlider.value);
initialDisplacement = parseFloat(displacementSlider.value);
massValue.textContent = mass.toFixed(1);
kValue.textContent = k;
dampingValue.textContent = damping.toFixed(2);
displacementValue.textContent = initialDisplacement;
// Reset position to initial displacement
currentY = equilibriumY + initialDisplacement * pixelsPerCm;
velocity = 0;
updatePhysicsInfo();
render();
}
// Update physics information display
function updatePhysicsInfo() {
const mass = parseFloat(massSlider.value);
const k = parseFloat(kSlider.value);
// Calculate natural frequency (rad/s)
const omega = Math.sqrt(k / mass);
// Convert to Hz
const freq = omega / (2 * Math.PI);
// Calculate period
const period = 2 * Math.PI / omega;
// Current position in cm (relative to equilibrium)
const positionCm = (currentY - equilibriumY) / pixelsPerCm;
frequencyEl.textContent = freq.toFixed(2);
periodEl.textContent = period.toFixed(2);
positionEl.textContent = positionCm.toFixed(1);
}
// Render the spring and mass
function render() {
// Spring height is from anchor to mass top
const springHeight = currentY - 20; // 20 is anchor height
spring.style.height = `${springHeight}px`;
spring.style.top = '20px'; // Below anchor
mass.style.top = `${currentY}px`;
mass.textContent = `${parseFloat(massSlider.value).toFixed(1)} kg`;
}
// Animation loop
function animate(timestamp) {
if (!lastTime) lastTime = timestamp;
const deltaTime = (timestamp - lastTime) / 1000; // Convert to seconds
lastTime = timestamp;
if (isOscillating) {
// Get current parameters
const mass = parseFloat(massSlider.value);
const k = parseFloat(kSlider.value);
const damping = parseFloat(dampingSlider.value);
// Calculate acceleration (F = ma = -kx - damping*v)
const displacement = (currentY - equilibriumY) / pixelsPerCm / 100; // Convert to meters
const acceleration = (-k * displacement - damping * velocity) / mass;
// Update velocity and position
velocity += acceleration * deltaTime;
currentY += velocity * pixelsPerCm * 100 * deltaTime; // Convert back to pixels
updatePhysicsInfo();
render();
}
animationId = requestAnimationFrame(animate);
}
// Event listeners
startBtn.addEventListener('click', () => {
if (!isOscillating) {
isOscillating = true;
lastTime = 0;
animationId = requestAnimationFrame(animate);
}
});
stopBtn.addEventListener('click', () => {
isOscillating = false;
if (animationId) {
cancelAnimationFrame(animationId);
}
});
resetBtn.addEventListener('click', () => {
isOscillating = false;
if (animationId) {
cancelAnimationFrame(animationId);
}
updateParameters();
});
// Slider event listeners
massSlider.addEventListener('input', updateParameters);
kSlider.addEventListener('input', updateParameters);
dampingSlider.addEventListener('input', updateParameters);
displacementSlider.addEventListener('input', updateParameters);
// Initialize
init();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=amirpoorazima/virtual-spring" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>