physics-simulations

A collection of physics simulations
git clone https://git.ashermorgan.net/physics-simulations/
Log | Files | Refs | README

commit 03dd97a034227481236b907cf2b823f6be9c075d
parent ebf2c9b7cd65f0c3b3936d8999669688ae001aaa
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Wed, 14 Jul 2021 17:04:39 -0700

Add simulation information pages

Diffstat:
Aimages/info.svg | 2++
Aimages/x.svg | 2++
Msimulations/atwood-machine.html | 122++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msimulations/atwood-machine.js | 21+++++++++++++++++++++
Msimulations/circular-motion.html | 116++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msimulations/circular-motion.js | 21+++++++++++++++++++++
Msimulations/horizontal-motion.html | 133+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msimulations/horizontal-motion.js | 23++++++++++++++++++++++-
Msimulations/projectile-motion.html | 144+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msimulations/projectile-motion.js | 21+++++++++++++++++++++
Msimulations/simple-pendulum.html | 111++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msimulations/simple-pendulum.js | 21+++++++++++++++++++++
Msimulations/spring-mass-system.html | 125++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msimulations/spring-mass-system.js | 21+++++++++++++++++++++
Msimulations/styles.css | 40+++++++++++++++++++++++++++++++++++++---
15 files changed, 643 insertions(+), 280 deletions(-)

diff --git a/images/info.svg b/images/info.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-info"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg> +\ No newline at end of file diff --git a/images/x.svg b/images/x.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg> +\ No newline at end of file diff --git a/simulations/atwood-machine.html b/simulations/atwood-machine.html @@ -15,6 +15,8 @@ <div id="app"> <header> <a title="Home" href="../" class="icon"><img alt="" src="../images/home.svg"></a> + <button v-show="!infoVisible" title="About" class="icon" @click="infoVisible=true"><img alt="" src="../images/info.svg"></button> + <button v-show="infoVisible" title="Close" class="icon" @click="infoVisible=false"><img alt="" src="../images/x.svg"></button> <h1>Atwood Machine</h1> </header> @@ -22,56 +24,86 @@ <p>This simulation requires JavaScript</p> </noscript> - <div id="input" hidden> - <section> - <label for="mass1Input"><b>Mass 1:</b> {{ mass1.toFixed(1) }} Kg</label> - <input type="range" min="1" max="10" step="0.1" v-model.number="mass1" @input="reset" @dblclick="mass1=1" :disabled="active" id="mass1Input"> - </section> - <section> - <label for="mass2Input"><b>Mass 2:</b> {{ mass2.toFixed(1) }} Kg</label> - <input type="range" min="1" max="10" step="0.1" v-model.number="mass2" @input="reset" @dblclick="mass2=10" :disabled="active" id="mass2Input"> - </section> - <section> - <label for="angleInput"><b>Angle:</b> {{ angle.toFixed(0) }}<sup>o</sup></label> - <input type="range" min="0" max="90" step="1" v-model.number="angle" @input="reset" @dblclick="angle=90" :disabled="active" id="angleInput"> - </section> - <section> - <label for="gravityInput"><b>Gravity:</b> {{ gravity.toFixed(1) }} m/s<sup>2</sup></label> - <input type="range" min="1" max="10" step="0.1" v-model.number="gravity" @input="reset" @dblclick="gravity=9.8" :disabled="active" id="gravityInput"> - </section> - </div> - - <div id="output" hidden> - <div id="simControls"> - <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')" :disabled="Math.abs(displacement) === 3"> - <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> - </button> - <button @click="update" class="icon" title="Step Forward" :disabled="Math.abs(displacement) === 3 || active"> - <img alt="" src="../images/step-forward.svg"> - </button> - <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> - <img alt="" src="../images/reset.svg"> - </button> + <div id="simulation" v-show="!infoVisible"> + <div id="input" hidden> + <section> + <label for="mass1Input"><b>Mass 1:</b> {{ mass1.toFixed(1) }} Kg</label> + <input type="range" min="1" max="10" step="0.1" v-model.number="mass1" @input="reset" @dblclick="mass1=1" :disabled="active" id="mass1Input"> + </section> + <section> + <label for="mass2Input"><b>Mass 2:</b> {{ mass2.toFixed(1) }} Kg</label> + <input type="range" min="1" max="10" step="0.1" v-model.number="mass2" @input="reset" @dblclick="mass2=10" :disabled="active" id="mass2Input"> + </section> + <section> + <label for="angleInput"><b>Angle:</b> {{ angle.toFixed(0) }}<sup>o</sup></label> + <input type="range" min="0" max="90" step="1" v-model.number="angle" @input="reset" @dblclick="angle=90" :disabled="active" id="angleInput"> + </section> + <section> + <label for="gravityInput"><b>Gravity:</b> {{ gravity.toFixed(1) }} m/s<sup>2</sup></label> + <input type="range" min="1" max="10" step="0.1" v-model.number="gravity" @input="reset" @dblclick="gravity=9.8" :disabled="active" id="gravityInput"> + </section> </div> - <svg width="400px" viewBox="-0.1 -0.1 8.3 8.1"> - <!-- Pulley --> - <circle cx="1.5" cy="1.5" r="1" stroke-width="0.1" stroke="#808080" fill="#404040"></circle> - <!-- 1st weight --> - <line x1="0.5" y1="1.5" x2="0.5" :y2="4.5 + displacement" stroke-width="0.1" stroke="#808080"></line> - <circle cx="0.5" :cy="4.5 + displacement" :r="0.03*mass1+0.2" fill="#ff0000"></circle> + <div id="output" hidden> + <div id="simControls"> + <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')" :disabled="Math.abs(displacement) === 3"> + <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> + </button> + <button @click="update" class="icon" title="Step Forward" :disabled="Math.abs(displacement) === 3 || active"> + <img alt="" src="../images/step-forward.svg"> + </button> + <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> + <img alt="" src="../images/reset.svg"> + </button> + </div> + <svg width="400px" viewBox="-0.1 -0.1 8.3 8.1"> + <!-- Pulley --> + <circle cx="1.5" cy="1.5" r="1" stroke-width="0.1" stroke="#808080" fill="#404040"></circle> + + <!-- 1st weight --> + <line x1="0.5" y1="1.5" x2="0.5" :y2="4.5 + displacement" stroke-width="0.1" stroke="#808080"></line> + <circle cx="0.5" :cy="4.5 + displacement" :r="0.03*mass1+0.2" fill="#ff0000"></circle> + + <!-- 2nd weight --> + <line :x1="positionVector[0] + 1.5" :y1="positionVector[1] + 1.5" :x2="positionVector[2] + 1.5" :y2="positionVector[3] + 1.5" stroke-width="0.1" stroke="#808080"></line> + <circle :cx="positionVector[2] + 1.5" :cy="positionVector[3] + 1.5" :r="0.03*mass2+0.2" fill="#0000ff"></circle> + </svg> + </div> - <!-- 2nd weight --> - <line :x1="positionVector[0] + 1.5" :y1="positionVector[1] + 1.5" :x2="positionVector[2] + 1.5" :y2="positionVector[3] + 1.5" stroke-width="0.1" stroke="#808080"></line> - <circle :cx="positionVector[2] + 1.5" :cy="positionVector[3] + 1.5" :r="0.03*mass2+0.2" fill="#0000ff"></circle> - </svg> + <div id="data" hidden> + <label><b>Time:</b> {{ time.toFixed(2) }} s</label> + <label><b>Displacement:</b> {{ Math.abs(displacement).toFixed(2) }} m</label> + <label><b>Velocity:</b> {{ velocity.toFixed(2) }} m/s</label> + <label><b>Acceleration:</b> {{ acceleration.toFixed(2) }} m/s<sup>2</sup></label> + </div> </div> - <div id="data" hidden> - <label><b>Time:</b> {{ time.toFixed(2) }} s</label> - <label><b>Displacement:</b> {{ Math.abs(displacement).toFixed(2) }} m</label> - <label><b>Velocity:</b> {{ velocity.toFixed(2) }} m/s</label> - <label><b>Acceleration:</b> {{ acceleration.toFixed(2) }} m/s<sup>2</sup></label> + <div id="info" v-show="infoVisible" hidden> + <h2>Simulation Information</h2> + <p>This simulation consists of two weights connected by a massless string over an ideal massless pulley.</p> + + <p>You can control the simulation using these variables:</p> + <ul> + <li><b>Mass 1:</b> The mass of the red weight</li> + <li><b>Mass 2:</b> The mass of the blue weight</li> + <li><b>Angle:</b> The angle of the incline that the blue weight sits on</li> + <li><b>Gravity:</b> The acceleration due to gravity</li> + </ul> + + <p>The simulation also contains these output measurements:</p> + <ul> + <li><b>Time:</b> The time that has passed</li> + <li><b>Displacement:</b> The current displacement of the blue weight</li> + <li><b>Velocity:</b> The current velocity of the blue weight</li> + <li><b>Acceleration:</b> The current acceleration of the blue weight</li> + </ul> + + <p>Controls:</p> + <ul> + <li>Use the start <img alt="" src="../images/play.svg"> and stop <img alt="" src="../images/pause.svg"> buttons to start and stop the simulation</li> + <li>Use the step <img alt="" src="../images/step-forward.svg"> button to advance the simulation by 0.01 seconds</li> + <li>Use the reset <img alt="" src="../images/reset.svg"> button to restore the simulation to its initial state</li> + </ul> </div> </div> </body> diff --git a/simulations/atwood-machine.js b/simulations/atwood-machine.js @@ -9,6 +9,7 @@ const App = { active: false, // Whether the simulation is active refreshRate: 0.01, // The simulation refresh rate (s) intervalId: null, // The value returned by setInterval + infoVisible: false, } }, computed: { @@ -59,6 +60,17 @@ const App = { }, methods: { /** + * Handle a keyup event (implements keyboard shortcuts) + * @param {object} e - The event args + */ + keyup: function(e) { + if (e.key === "Escape") { + if (this.infoVisible) this.infoVisible = false; + else window.location.href = "../"; + } + }, + + /** * Toggle whether the simulation is active */ toggle: function() { @@ -101,6 +113,14 @@ const App = { return Math.cos(angle / 360 * 2 * Math.PI) * distance; }, }, + created: function() { + // Add keyup handler + window.addEventListener("keyup", this.keyup); + }, + destroyed: function() { + // Remove keyup handler + window.removeEventListener("keyup", this.keyup); + }, } @@ -114,4 +134,5 @@ function createApp() { document.getElementById("input").hidden = false; document.getElementById("output").hidden = false; document.getElementById("data").hidden = false; + document.getElementById("info").hidden = false; } diff --git a/simulations/circular-motion.html b/simulations/circular-motion.html @@ -15,6 +15,8 @@ <div id="app"> <header> <a title="Home" href="../" class="icon"><img alt="" src="../images/home.svg"></a> + <button v-show="!infoVisible" title="About" class="icon" @click="infoVisible=true"><img alt="" src="../images/info.svg"></button> + <button v-show="infoVisible" title="Close" class="icon" @click="infoVisible=false"><img alt="" src="../images/x.svg"></button> <h1>Circular Motion</h1> </header> @@ -22,55 +24,83 @@ <p>This simulation requires JavaScript</p> </noscript> - <div id="input" hidden> - <section> - <label for="massInput"><b>Mass:</b> {{ mass.toFixed(1) }} Kg</label> - <input type="range" min="1" max="10" step="0.1" v-model.number="mass" @input="reset" @dblclick="mass=5" :disabled="active" id="massInput"> - </section> - <section> - <label for="forceInput"><b>Centripetal Force:</b> {{ force.toFixed(1) }} N</label> - <input type="range" min="1" max="10" step="0.1" v-model.number="force" @input="reset" @dblclick="force=5" :disabled="active" id="forceInput"> - </section> - <section> - <label for="radiusInput"><b>Radius:</b> {{ radius.toFixed(2) }} m</label> - <input type="range" min="0.1" max="1" step="0.01" v-model.number="radius" @input="reset" @dblclick="radius=1" :disabled="active" id="radiusInput"> - </section> - </div> - - <div id="output" hidden> - <div id="simControls"> - <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')"> - <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> - </button> - <button @click="update" class="icon" title="Step Forward" :disabled="active"> - <img alt="" src="../images/step-forward.svg"> - </button> - <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> - <img alt="" src="../images/reset.svg"> - </button> + <div id="simulation" v-show="!infoVisible"> + <div id="input" hidden> + <section> + <label for="massInput"><b>Mass:</b> {{ mass.toFixed(1) }} Kg</label> + <input type="range" min="1" max="10" step="0.1" v-model.number="mass" @input="reset" @dblclick="mass=5" :disabled="active" id="massInput"> + </section> + <section> + <label for="forceInput"><b>Centripetal Force:</b> {{ force.toFixed(1) }} N</label> + <input type="range" min="1" max="10" step="0.1" v-model.number="force" @input="reset" @dblclick="force=5" :disabled="active" id="forceInput"> + </section> + <section> + <label for="radiusInput"><b>Radius:</b> {{ radius.toFixed(2) }} m</label> + <input type="range" min="0.1" max="1" step="0.01" v-model.number="radius" @input="reset" @dblclick="radius=1" :disabled="active" id="radiusInput"> + </section> </div> - <svg width="400px" viewBox="-1.5 -1.5 3 3"> - <!-- Circle outline --> - <circle cx="0" cy="0" :r="radius" stroke="#808080" stroke-width="0.01" fill="none" stroke-dasharray="0.05,0.05"/> - <circle cx="0" cy="0" r="0.025" fill="#000000"></circle> - <!-- Velocity vector--> - <line :x1="position[0]" :y1="position[1]" :x2="velocityVector[0]" :y2="velocityVector[1]" stroke="black" stroke-width="0.025"/> - <path :d="`M${velocityVector[2]} ${velocityVector[3]} L${velocityVector[0]} ${velocityVector[1]} L${velocityVector[4]} ${velocityVector[5]} Z`" stroke="#000000" stroke-width="0.025" fill="#000000"/> + <div id="output" hidden> + <div id="simControls"> + <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')"> + <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> + </button> + <button @click="update" class="icon" title="Step Forward" :disabled="active"> + <img alt="" src="../images/step-forward.svg"> + </button> + <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> + <img alt="" src="../images/reset.svg"> + </button> + </div> + <svg width="400px" viewBox="-1.5 -1.5 3 3"> + <!-- Circle outline --> + <circle cx="0" cy="0" :r="radius" stroke="#808080" stroke-width="0.01" fill="none" stroke-dasharray="0.05,0.05"/> + <circle cx="0" cy="0" r="0.025" fill="#000000"></circle> + + <!-- Velocity vector--> + <line :x1="position[0]" :y1="position[1]" :x2="velocityVector[0]" :y2="velocityVector[1]" stroke="black" stroke-width="0.025"/> + <path :d="`M${velocityVector[2]} ${velocityVector[3]} L${velocityVector[0]} ${velocityVector[1]} L${velocityVector[4]} ${velocityVector[5]} Z`" stroke="#000000" stroke-width="0.025" fill="#000000"/> - <!-- Centripetal force vector--> - <line :x1="position[0]" :y1="position[1]" :x2="forceVector[0]" :y2="forceVector[1]" stroke="gray" stroke-width="0.025"/> - <path :d="`M${forceVector[2]} ${forceVector[3]} L${forceVector[0]} ${forceVector[1]} L${forceVector[4]} ${forceVector[5]} Z`" stroke="#808080" stroke-width="0.025" fill="#808080"/> + <!-- Centripetal force vector--> + <line :x1="position[0]" :y1="position[1]" :x2="forceVector[0]" :y2="forceVector[1]" stroke="gray" stroke-width="0.025"/> + <path :d="`M${forceVector[2]} ${forceVector[3]} L${forceVector[0]} ${forceVector[1]} L${forceVector[4]} ${forceVector[5]} Z`" stroke="#808080" stroke-width="0.025" fill="#808080"/> + + <!-- Object --> + <circle :cx="position[0]" :cy="position[1]" :r="(0.005*mass)+0.025" fill="#ff0000"/> + </svg> + </div> - <!-- Object --> - <circle :cx="position[0]" :cy="position[1]" :r="(0.005*mass)+0.025" fill="#ff0000"/> - </svg> + <div id="data" hidden> + <label><b>Time:</b> {{ time.toFixed(2) }} s</label> + <label><b>Speed:</b> {{ speed.toFixed(2) }} m/s</label> + <label><b>Period:</b> {{ period.toFixed(2) }} s</label> + </div> </div> - <div id="data" hidden> - <label><b>Time:</b> {{ time.toFixed(2) }} s</label> - <label><b>Speed:</b> {{ speed.toFixed(2) }} m/s</label> - <label><b>Period:</b> {{ period.toFixed(2) }} s</label> + <div id="info" v-show="infoVisible" hidden> + <h2>Simulation Information</h2> + <p>This simulation consists of an object that is spun in a circle by a centripetal force.</p> + + <p>You can control the simulation using these variables:</p> + <ul> + <li><b>Mass:</b> The mass of the object</li> + <li><b>Centripetal Force:</b> The magnitude of the centripetal force</li> + <li><b>Radius:</b> The radius of the circle</li> + </ul> + + <p>The simulation also contains these output measurements:</p> + <ul> + <li><b>Time:</b> The time that has passed</li> + <li><b>Speed:</b> The current speed of the object</li> + <li><b>Period:</b> The period of the object</li> + </ul> + + <p>Controls:</p> + <ul> + <li>Use the start <img alt="" src="../images/play.svg"> and stop <img alt="" src="../images/pause.svg"> buttons to start and stop the simulation</li> + <li>Use the step <img alt="" src="../images/step-forward.svg"> button to advance the simulation by 0.01 seconds</li> + <li>Use the reset <img alt="" src="../images/reset.svg"> button to restore the simulation to its initial state</li> + </ul> </div> </div> </body> diff --git a/simulations/circular-motion.js b/simulations/circular-motion.js @@ -8,6 +8,7 @@ const App = { active: false, // Whether the simulation is active refreshRate: 0.01, // The simulation refresh rate (s) intervalId: null, // The value returned by setInterval + infoVisible: false, } }, computed: { @@ -84,6 +85,17 @@ const App = { }, methods: { /** + * Handle a keyup event (implements keyboard shortcuts) + * @param {object} e - The event args + */ + keyup: function(e) { + if (e.key === "Escape") { + if (this.infoVisible) this.infoVisible = false; + else window.location.href = "../"; + } + }, + + /** * Toggle whether the simulation is active */ toggle: function() { @@ -126,6 +138,14 @@ const App = { return -Math.cos(angle * 2 * Math.PI) * distance; }, }, + created: function() { + // Add keyup handler + window.addEventListener("keyup", this.keyup); + }, + destroyed: function() { + // Remove keyup handler + window.removeEventListener("keyup", this.keyup); + }, } @@ -139,4 +159,5 @@ function createApp() { document.getElementById("input").hidden = false; document.getElementById("output").hidden = false; document.getElementById("data").hidden = false; + document.getElementById("info").hidden = false; } diff --git a/simulations/horizontal-motion.html b/simulations/horizontal-motion.html @@ -15,6 +15,8 @@ <div id="app"> <header> <a title="Home" href="../" class="icon"><img alt="" src="../images/home.svg"></a> + <button v-show="!infoVisible" title="About" class="icon" @click="infoVisible=true"><img alt="" src="../images/info.svg"></button> + <button v-show="infoVisible" title="Close" class="icon" @click="infoVisible=false"><img alt="" src="../images/x.svg"></button> <h1>Horizontal Motion</h1> </header> @@ -22,61 +24,92 @@ <p>This simulation requires JavaScript</p> </noscript> - <div id="input" hidden> - <section> - <label for="massInput"><b>Mass:</b> {{ mass.toFixed(1) }} Kg</label> - <input type="range" min="1" max="10" step="0.1" v-model.number="mass" @dblclick="mass=5" id="massInput"> - </section> - <section> - <label for="forceInput"><b>Applied Force:</b> {{ force.toFixed(1) }} N</label> - <input type="range" min="-10" max="10" step="0.1" v-model.number="force" @dblclick="force=0" id="forceInput"> - </section> - <section> - <label for="staticFrictionInput"><b>Static Friction:</b> {{ staticFriction.toFixed(2) }}</label> - <input type="range" min="0" max="1" step="0.01" v-model.number="staticFriction" @dblclick="staticFriction=0" id="staticFrictionInput"> - </section> - <section> - <label for="kineticFrictionInput"><b>Kinetic Friction:</b> {{ kineticFriction.toFixed(2) }}</label> - <input type="range" min="0" max="1" step="0.01" v-model.number="kineticFriction" @dblclick="kineticFriction=0" id="kineticFrictionInput"> - </section> - <section> - <label for="gravityInput"><b>Gravity:</b> {{ gravity.toFixed(1) }} m/s<sup>2</sup></label> - <input type="range" min="0" max="10" step="0.1" v-model.number="gravity" @dblclick="gravity=9.8" id="gravityInput"> - </section> - </div> - - <div id="output" hidden> - <div id="simControls"> - <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')"> - <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> - </button> - <button @click="update" class="icon" title="Step Forward" :disabled="active"> - <img alt="" src="../images/step-forward.svg"> - </button> - <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> - <img alt="" src="../images/reset.svg"> - </button> + <div id="simulation" v-show="!infoVisible"> + <div id="input" hidden> + <section> + <label for="massInput"><b>Mass:</b> {{ mass.toFixed(1) }} Kg</label> + <input type="range" min="1" max="10" step="0.1" v-model.number="mass" @dblclick="mass=5" id="massInput"> + </section> + <section> + <label for="forceInput"><b>Applied Force:</b> {{ force.toFixed(1) }} N</label> + <input type="range" min="-10" max="10" step="0.1" v-model.number="force" @dblclick="force=0" id="forceInput"> + </section> + <section> + <label for="staticFrictionInput"><b>Static Friction:</b> {{ staticFriction.toFixed(2) }}</label> + <input type="range" min="0" max="1" step="0.01" v-model.number="staticFriction" @dblclick="staticFriction=0" id="staticFrictionInput"> + </section> + <section> + <label for="kineticFrictionInput"><b>Kinetic Friction:</b> {{ kineticFriction.toFixed(2) }}</label> + <input type="range" min="0" max="1" step="0.01" v-model.number="kineticFriction" @dblclick="kineticFriction=0" id="kineticFrictionInput"> + </section> + <section> + <label for="gravityInput"><b>Gravity:</b> {{ gravity.toFixed(1) }} m/s<sup>2</sup></label> + <input type="range" min="0" max="10" step="0.1" v-model.number="gravity" @dblclick="gravity=9.8" id="gravityInput"> + </section> </div> - <svg width="800px" viewBox="0 0 10 2"> - <!-- Ruler marks --> - <line v-for="(_,n) in 101" :x1="references[1] + n*0.1" y1="1.7" :x2="references[1] + n*0.1" y2="1.9" stroke-width="0.01" stroke="#000000"></line> - <line v-for="(_,n) in 11" :x1="references[0] + n" y1="1.6" :x2="references[0] + n" y2="2.0" stroke-width="0.02" stroke="#000000"></line> - <!-- Force vector --> - <line x1="5" :y1="1.2 - 0.05*mass+0.1" :x2="5+0.3*force" :y2="1.2 - 0.05*mass+0.1" stroke-width="0.1" stroke="#808080"></line> - <path v-show="force&gt;0" :d="`M${5+0.3*force} ${1.3 - 0.05*mass+0.1} L${5.1+0.3*force} ${1.2 - 0.05*mass+0.1} L${5+0.3*force} ${1.1 - 0.05*mass+0.1} Z`" stroke="#808080" stroke-width="0.025" fill="#808080"/> - <path v-show="force&lt;0" :d="`M${5+0.3*force} ${1.3 - 0.05*mass+0.1} L${4.9+0.3*force} ${1.2 - 0.05*mass+0.1} L${5+0.3*force} ${1.1 - 0.05*mass+0.1} Z`" stroke="#808080" stroke-width="0.025" fill="#808080"/> + <div id="output" hidden> + <div id="simControls"> + <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')"> + <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> + </button> + <button @click="update" class="icon" title="Step Forward" :disabled="active"> + <img alt="" src="../images/step-forward.svg"> + </button> + <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> + <img alt="" src="../images/reset.svg"> + </button> + </div> + <svg width="800px" viewBox="0 0 10 2"> + <!-- Ruler marks --> + <line v-for="(_,n) in 101" :x1="references[1] + n*0.1" y1="1.7" :x2="references[1] + n*0.1" y2="1.9" stroke-width="0.01" stroke="#000000"></line> + <line v-for="(_,n) in 11" :x1="references[0] + n" y1="1.6" :x2="references[0] + n" y2="2.0" stroke-width="0.02" stroke="#000000"></line> + + <!-- Force vector --> + <line x1="5" :y1="1.2 - 0.05*mass+0.1" :x2="5+0.3*force" :y2="1.2 - 0.05*mass+0.1" stroke-width="0.1" stroke="#808080"></line> + <path v-show="force&gt;0" :d="`M${5+0.3*force} ${1.3 - 0.05*mass+0.1} L${5.1+0.3*force} ${1.2 - 0.05*mass+0.1} L${5+0.3*force} ${1.1 - 0.05*mass+0.1} Z`" stroke="#808080" stroke-width="0.025" fill="#808080"/> + <path v-show="force&lt;0" :d="`M${5+0.3*force} ${1.3 - 0.05*mass+0.1} L${4.9+0.3*force} ${1.2 - 0.05*mass+0.1} L${5+0.3*force} ${1.1 - 0.05*mass+0.1} Z`" stroke="#808080" stroke-width="0.025" fill="#808080"/> + + <!-- Object --> + <circle cx="5" :cy="1.2 - 0.05*mass+0.1" :r="0.05*mass+0.1" fill="#ff0000"></circle> + </svg> + </div> - <!-- Object --> - <circle cx="5" :cy="1.2 - 0.05*mass+0.1" :r="0.05*mass+0.1" fill="#ff0000"></circle> - </svg> + <div id="data" hidden> + <label><b>Time:</b> {{ time.toFixed(2) }} s</label> + <label><b>Position:</b> {{ position.toFixed(2) }} m</label> + <label><b>Velocity:</b> {{ velocity.toFixed(2) }} m/s</label> + <label><b>Acceleration:</b> {{ acceleration.toFixed(2) }} m/s<sup>2</sup></label> + </div> </div> - <div id="data" hidden> - <label><b>Time:</b> {{ time.toFixed(2) }} s</label> - <label><b>Position:</b> {{ position.toFixed(2) }} m</label> - <label><b>Velocity:</b> {{ velocity.toFixed(2) }} m/s</label> - <label><b>Acceleration:</b> {{ acceleration.toFixed(2) }} m/s<sup>2</sup></label> + <div id="info" v-show="infoVisible" hidden> + <h2>Simulation Information</h2> + <p>This simulation consists an object that can be accelerated horizontally along a surface.</p> + + <p>You can control the simulation using these variables:</p> + <ul> + <li><b>Mass:</b> The mass of the object</li> + <li><b>Applied Force:</b> The force applied on the ball</li> + <li><b>Static Friction:</b> The coefficient of static friction</li> + <li><b>Kinetic Friction:</b> The coefficient of kinetic friction</li> + <li><b>Gravity:</b> The acceleration due to gravity</li> + </ul> + + <p>The simulation also contains these output measurements:</p> + <ul> + <li><b>Time:</b> The time that has passed</li> + <li><b>Position:</b> The current displacement of the object</li> + <li><b>Velocity:</b> The current velocity of the object</li> + <li><b>Acceleration:</b> The current acceleration of the object</li> + </ul> + + <p>Controls:</p> + <ul> + <li>Use the start <img alt="" src="../images/play.svg"> and stop <img alt="" src="../images/pause.svg"> buttons to start and stop the simulation</li> + <li>Use the step <img alt="" src="../images/step-forward.svg"> button to advance the simulation by 0.01 seconds</li> + <li>Use the reset <img alt="" src="../images/reset.svg"> button to restore the simulation to its initial state</li> + </ul> </div> </div> </body> diff --git a/simulations/horizontal-motion.js b/simulations/horizontal-motion.js @@ -17,6 +17,7 @@ const App = { active: false, // Whether the simulation is active refreshRate: 0.01, // The simulation refresh rate (s) intervalId: null, // The value returned by setInterval + infoVisible: false, } }, computed: { @@ -73,6 +74,17 @@ const App = { }, methods: { /** + * Handle a keyup event (implements keyboard shortcuts) + * @param {object} e - The event args + */ + keyup: function(e) { + if (e.key === "Escape") { + if (this.infoVisible) this.infoVisible = false; + else window.location.href = "../"; + } + }, + + /** * Toggle whether the simulation is active */ toggle: function() { @@ -96,7 +108,7 @@ const App = { update: function() { // Update time this.time += this.refreshRate; - + // Get updated velocity let newVelocity = this.velocity + (this.acceleration * this.refreshRate) @@ -119,6 +131,14 @@ const App = { this.position += (this.velocity * this.refreshRate); }, }, + created: function() { + // Add keyup handler + window.addEventListener("keyup", this.keyup); + }, + destroyed: function() { + // Remove keyup handler + window.removeEventListener("keyup", this.keyup); + }, } @@ -132,4 +152,5 @@ function createApp() { document.getElementById("input").hidden = false; document.getElementById("output").hidden = false; document.getElementById("data").hidden = false; + document.getElementById("info").hidden = false; } diff --git a/simulations/projectile-motion.html b/simulations/projectile-motion.html @@ -17,6 +17,8 @@ <div id="app"> <header> <a title="Home" href="../" class="icon"><img alt="" src="../images/home.svg"></a> + <button v-show="!infoVisible" title="About" class="icon" @click="infoVisible=true"><img alt="" src="../images/info.svg"></button> + <button v-show="infoVisible" title="Close" class="icon" @click="infoVisible=false"><img alt="" src="../images/x.svg"></button> <h1>Projectile Motion</h1> </header> @@ -24,68 +26,100 @@ <p>This simulation requires JavaScript</p> </noscript> - <div id="input" hidden> - <section> - <label for="heightInput"><b>Height:</b> {{ height.toFixed(1) }} m</label> - <input type="range" min="0" max="10" step="0.1" v-model.number="height" @input="reset" :disabled="active" @dblclick="height=1" id="heightInput"> - </section> - <section> - <label for="initialVelocityInput"><b>Velocity:</b> {{ initialVelocity.toFixed(1) }} m/s</label> - <input type="range" min="0" max="10" step="0.1" v-model.number="initialVelocity" @input="reset" :disabled="active" @dblclick="initialVelocity=5" id="initialVelocityInput"> - </section> - <section> - <label for="angleInput"><b>Angle:</b> {{ angle.toFixed(0) }}<sup>o</sup></label> - <input type="range" min="-90" max="90" step="1" v-model.number="angle" @input="reset" :disabled="active" @dblclick="angle=0" id="angleInput"> - </section> - <section> - <label for="gravityInput"><b>Gravity:</b> {{ gravity.toFixed(1) }} m/s<sup>2</sup></label> - <input type="range" min="0" max="10" step="0.1" v-model.number="gravity" @input="reset" :disabled="active" @dblclick="gravity=9.8" id="gravityInput"> - </section> - </div> + <div id="simulation" v-show="!infoVisible"> + <div id="input" hidden> + <section> + <label for="heightInput"><b>Height:</b> {{ height.toFixed(1) }} m</label> + <input type="range" min="0" max="10" step="0.1" v-model.number="height" @input="reset" :disabled="active" @dblclick="height=1" id="heightInput"> + </section> + <section> + <label for="initialVelocityInput"><b>Velocity:</b> {{ initialVelocity.toFixed(1) }} m/s</label> + <input type="range" min="0" max="10" step="0.1" v-model.number="initialVelocity" @input="reset" :disabled="active" @dblclick="initialVelocity=5" id="initialVelocityInput"> + </section> + <section> + <label for="angleInput"><b>Angle:</b> {{ angle.toFixed(0) }}<sup>o</sup></label> + <input type="range" min="-90" max="90" step="1" v-model.number="angle" @input="reset" :disabled="active" @dblclick="angle=0" id="angleInput"> + </section> + <section> + <label for="gravityInput"><b>Gravity:</b> {{ gravity.toFixed(1) }} m/s<sup>2</sup></label> + <input type="range" min="0" max="10" step="0.1" v-model.number="gravity" @input="reset" :disabled="active" @dblclick="gravity=9.8" id="gravityInput"> + </section> + </div> + + <div id="output" hidden> + <div id="simControls"> + <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')" :disabled="time !== 0 && position.y &lt;= 0"> + <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> + </button> + <button @click="update" class="icon" title="Step Forward" :disabled="(time !== 0 && position.y &lt;= 0) || active"> + <img alt="" src="../images/step-forward.svg"> + </button> + <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> + <img alt="" src="../images/reset.svg"> + </button> + </div> + <div id="zoomControls"> + <button @click="svg.zoomOut()" class="icon" title="Zoom Out"> + <img alt="" src="../images/zoom-out.svg"> + </button> + <button @click="svg.resetZoom();svg.resetPan()" class="icon" title="Reset Zoom"> + <img alt="" src="../images/home.svg"> + </button> + <button @click="svg.zoomIn()" class="icon" title="Zoom In"> + <img alt="" src="../images/zoom-in.svg"> + </button> + </div> + <svg width="400px" height="400px" viewBox="-1 -1 101 102"> + <!-- Ground --> + <line x1="-10000" y1="101" x2="10000" y2="101" stroke-width="1" stroke="#404040"></line> + + <!-- Path --> + <circle v-for="position in positions" :cx="10*position.x" :cy="100 - 10*position.y" r="0.5" fill="#808080"></circle> - <div id="output" hidden> - <div id="simControls"> - <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')" :disabled="time !== 0 && position.y &lt;= 0"> - <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> - </button> - <button @click="update" class="icon" title="Step Forward" :disabled="(time !== 0 && position.y &lt;= 0) || active"> - <img alt="" src="../images/step-forward.svg"> - </button> - <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> - <img alt="" src="../images/reset.svg"> - </button> + <!-- Launch vector --> + <line :x1="10*position.x" :y1="100 - 10*position.y" :x2="10*launchArrow[1].x" :y2="100 - 10*launchArrow[1].y" stroke-width="1" stroke="#808080"></line> + <path :d="`M${10*launchArrow[0].x} ${100 - 10*launchArrow[0].y} L${10*launchArrow[1].x} ${100 - 10*launchArrow[1].y} L${10*launchArrow[2].x} ${100 - 10*launchArrow[2].y} Z`" stroke="#808080" stroke-width="1" fill="#808080"/> + + <!-- Projectile --> + <circle :cx="10*position.x" :cy="100 - 10*position.y" r="1" fill="#ff0000"></circle> + </svg> </div> - <div id="zoomControls"> - <button @click="svg.zoomOut()" class="icon" title="Zoom Out"> - <img alt="" src="../images/zoom-out.svg"> - </button> - <button @click="svg.resetZoom();svg.resetPan()" class="icon" title="Reset Zoom"> - <img alt="" src="../images/home.svg"> - </button> - <button @click="svg.zoomIn()" class="icon" title="Zoom In"> - <img alt="" src="../images/zoom-in.svg"> - </button> + + <div id="data" hidden> + <label><b>Time:</b> {{ time.toFixed(2) }} s</label> + <label><b>Position:</b> {{ position.x.toFixed(2) }} m, {{ position.y > 0 ? position.y.toFixed(2) : 0.0.toFixed(2) }} m</label> + <label><b>Velocity:</b> {{ velocity.total.toFixed(2) }} m/s at {{ velocity.angle.toFixed(1) }}<sup>o</sup></label> </div> - <svg width="400px" height="400px" viewBox="-1 -1 101 102"> - <!-- Ground --> - <line x1="-10000" y1="101" x2="10000" y2="101" stroke-width="1" stroke="#404040"></line> + </div> - <!-- Path --> - <circle v-for="position in positions" :cx="10*position.x" :cy="100 - 10*position.y" r="0.5" fill="#808080"></circle> + <div id="info" v-show="infoVisible" hidden> + <h2>Simulation Information</h2> + <p>This simulation consists of a projectile that can be launched.</p> - <!-- Launch vector --> - <line :x1="10*position.x" :y1="100 - 10*position.y" :x2="10*launchArrow[1].x" :y2="100 - 10*launchArrow[1].y" stroke-width="1" stroke="#808080"></line> - <path :d="`M${10*launchArrow[0].x} ${100 - 10*launchArrow[0].y} L${10*launchArrow[1].x} ${100 - 10*launchArrow[1].y} L${10*launchArrow[2].x} ${100 - 10*launchArrow[2].y} Z`" stroke="#808080" stroke-width="1" fill="#808080"/> + <p>You can control the simulation using these variables:</p> + <ul> + <li><b>Height:</b> The initial height of the projectile</li> + <li><b>Velocity:</b> The initial velocity of the projectile</li> + <li><b>Angle:</b> The angle to launch the projectile at</li> + <li><b>Gravity:</b> The acceleration due to gravity</li> + </ul> - <!-- Projectile --> - <circle :cx="10*position.x" :cy="100 - 10*position.y" r="1" fill="#ff0000"></circle> - </svg> - </div> + <p>The simulation also contains these output measurements:</p> + <ul> + <li><b>Time:</b> The time that has passed</li> + <li><b>Position:</b> The current position of the projectile</li> + <li><b>Velocity:</b> The current velocity of the projectile</li> + </ul> - <div id="data" hidden> - <label><b>Time:</b> {{ time.toFixed(2) }} s</label> - <label><b>Position:</b> {{ position.x.toFixed(2) }} m, {{ position.y > 0 ? position.y.toFixed(2) : 0.0.toFixed(2) }} m</label> - <label><b>Velocity:</b> {{ velocity.total.toFixed(2) }} m/s at {{ velocity.angle.toFixed(1) }}<sup>o</sup></label> + <p>Controls:</p> + <ul> + <li>Use the start <img alt="" src="../images/play.svg"> and stop <img alt="" src="../images/pause.svg"> buttons to start and stop the simulation</li> + <li>Use the step <img alt="" src="../images/step-forward.svg"> button to advance the simulation by 0.01 seconds</li> + <li>Use the reset <img alt="" src="../images/reset.svg"> button to restore the simulation to its initial state</li> + <li>Click and drag the simulation to pan</li> + <li>Use the zoom in <img alt="" src="../images/zoom-in.svg"> and zoom out <img alt="" src="../images/zoom-out.svg"> buttons or the mouse wheel to zoom in and out</li> + <li>Use the reset zoom <img alt="" src="../images/home.svg"> button to undo all zooming and panning</li> + </ul> </div> </div> </body> diff --git a/simulations/projectile-motion.js b/simulations/projectile-motion.js @@ -13,6 +13,7 @@ const App = { active: false, // Whether the simulation is active refreshRate: 0.01, // The simulation refresh rate (s) intervalId: null, // The value returned by setInterval + infoVisible: false, svg: null, } }, @@ -65,6 +66,17 @@ const App = { }, methods: { /** + * Handle a keyup event (implements keyboard shortcuts) + * @param {object} e - The event args + */ + keyup: function(e) { + if (e.key === "Escape") { + if (this.infoVisible) this.infoVisible = false; + else window.location.href = "../"; + } + }, + + /** * Toggle whether the simulation is active */ toggle: function() { @@ -125,6 +137,14 @@ const App = { return Math.atan(opposite / adjacent) / (2 * Math.PI) * 360; }, }, + created: function() { + // Add keyup handler + window.addEventListener("keyup", this.keyup); + }, + destroyed: function() { + // Remove keyup handler + window.removeEventListener("keyup", this.keyup); + }, } @@ -138,6 +158,7 @@ function createApp() { document.getElementById("input").hidden = false; document.getElementById("output").hidden = false; document.getElementById("data").hidden = false; + document.getElementById("info").hidden = false; // Enable zooming let mobileZoomPanHandler = { diff --git a/simulations/simple-pendulum.html b/simulations/simple-pendulum.html @@ -15,6 +15,8 @@ <div id="app"> <header> <a title="Home" href="../" class="icon"><img alt="" src="../images/home.svg"></a> + <button v-show="!infoVisible" title="About" class="icon" @click="infoVisible=true"><img alt="" src="../images/info.svg"></button> + <button v-show="infoVisible" title="Close" class="icon" @click="infoVisible=false"><img alt="" src="../images/x.svg"></button> <h1>Simple Pendulum</h1> </header> @@ -22,51 +24,80 @@ <p>This simulation requires JavaScript</p> </noscript> - <div id="input" hidden> - <section> - <label for="radiusInput"><b>Radius:</b> {{ radius.toFixed(1) }} m</label> - <input type="range" min="1" max="10" step="0.1" v-model.number="radius" @input="reset" @dblclick="radius=5" :disabled="active" id="radiusInput"> - </section> - <section> - <label for="angleInput"><b>Initial Angle:</b> {{ initialAngle.toFixed(0) }}<sup>o</sup></label> - <input type="range" min="-45" max="45" step="1" v-model.number="initialAngle" @input="reset" @dblclick="initialAngle=0" :disabled="active" id="angleInput"> - </section> - <section> - <label for="gravityInput"><b>Gravity:</b> {{ gravity.toFixed(1) }} m/s<sup>2</sup></label> - <input type="range" min="0.1" max="10" step="0.1" v-model.number="gravity" @input="reset" @dblclick="gravity=9.8" :disabled="active" id="gravityInput"> - </section> - </div> - - <div id="output" hidden> - <div id="simControls"> - <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')"> - <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> - </button> - <button @click="update" class="icon" title="Step Forward" :disabled="active"> - <img alt="" src="../images/step-forward.svg"> - </button> - <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> - <img alt="" src="../images/reset.svg"> - </button> + <div id="simulation" v-show="!infoVisible"> + <div id="input" hidden> + <section> + <label for="radiusInput"><b>Radius:</b> {{ radius.toFixed(1) }} m</label> + <input type="range" min="1" max="10" step="0.1" v-model.number="radius" @input="reset" @dblclick="radius=5" :disabled="active" id="radiusInput"> + </section> + <section> + <label for="angleInput"><b>Initial Angle:</b> {{ initialAngle.toFixed(0) }}<sup>o</sup></label> + <input type="range" min="-45" max="45" step="1" v-model.number="initialAngle" @input="reset" @dblclick="initialAngle=0" :disabled="active" id="angleInput"> + </section> + <section> + <label for="gravityInput"><b>Gravity:</b> {{ gravity.toFixed(1) }} m/s<sup>2</sup></label> + <input type="range" min="0.1" max="10" step="0.1" v-model.number="gravity" @input="reset" @dblclick="gravity=9.8" :disabled="active" id="gravityInput"> + </section> </div> - <svg width="400px" viewBox="-8 -3 16 16"> - <!-- String --> - <circle cx="0" c1="0" r="0.1" fill="#808080"></circle> - <line x1="0" y1="0" :x2="position.x" :y2="-position.y" stroke="#808080" stroke-width="0.2"></line> - <!-- Box --> - <rect x="-2" y="-0.5" width="4" height="0.5" fill="#000000"></rect> + <div id="output" hidden> + <div id="simControls"> + <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')"> + <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> + </button> + <button @click="update" class="icon" title="Step Forward" :disabled="active"> + <img alt="" src="../images/step-forward.svg"> + </button> + <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> + <img alt="" src="../images/reset.svg"> + </button> + </div> + <svg width="400px" viewBox="-8 -3 16 16"> + <!-- String --> + <circle cx="0" c1="0" r="0.1" fill="#808080"></circle> + <line x1="0" y1="0" :x2="position.x" :y2="-position.y" stroke="#808080" stroke-width="0.2"></line> + + <!-- Box --> + <rect x="-2" y="-0.5" width="4" height="0.5" fill="#000000"></rect> + + <!-- Mass --> + <circle :cx="position.x" :cy="-position.y" r="0.5" fill="#ff0000"></circle> + </svg> + </div> - <!-- Mass --> - <circle :cx="position.x" :cy="-position.y" r="0.5" fill="#ff0000"></circle> - </svg> + <div id="data" hidden> + <label><b>Time:</b> {{ time.toFixed(2) }} s</label> + <label><b>Period:</b> {{ period.toFixed(2) }} s</label> + <label><b>Angle:</b> {{ angle.toFixed(0) }}<sup>o</sup></label> + <label><b>Acceleration:</b> {{ acceleration.toFixed(2) }} m/s<sup>2</sup></label> + </div> </div> - <div id="data" hidden> - <label><b>Time:</b> {{ time.toFixed(2) }} s</label> - <label><b>Period:</b> {{ period.toFixed(2) }} s</label> - <label><b>Angle:</b> {{ angle.toFixed(0) }}<sup>o</sup></label> - <label><b>Acceleration:</b> {{ acceleration.toFixed(2) }} m/s<sup>2</sup></label> + <div id="info" v-show="infoVisible" hidden> + <h2>Simulation Information</h2> + <p>This simulation consists of a mass suspended from a frictionless pivot by a massless string.</p> + + <p>You can control the simulation using these variables:</p> + <ul> + <li><b>Radius:</b> The length of the string</li> + <li><b>Initial Angle:</b> The initial angle of the pendulum</li> + <li><b>Gravity:</b> The acceleration due to gravity</li> + </ul> + + <p>The simulation also contains these output measurements:</p> + <ul> + <li><b>Time:</b> The time that has passed</li> + <li><b>Period:</b> The period of the pendulum</li> + <li><b>Angle:</b> The current angle of the pendulum</li> + <li><b>Acceleration:</b> The current acceleration of the mass</li> + </ul> + + <p>Controls:</p> + <ul> + <li>Use the start <img alt="" src="../images/play.svg"> and stop <img alt="" src="../images/pause.svg"> buttons to start and stop the simulation</li> + <li>Use the step <img alt="" src="../images/step-forward.svg"> button to advance the simulation by 0.01 seconds</li> + <li>Use the reset <img alt="" src="../images/reset.svg"> button to restore the simulation to its initial state</li> + </ul> </div> </div> </body> diff --git a/simulations/simple-pendulum.js b/simulations/simple-pendulum.js @@ -8,6 +8,7 @@ const App = { active: false, // Whether the simulation is active refreshRate: 0.01, // The simulation refresh rate (s) intervalId: null, // The value returned by setInterval + infoVisible: false, } }, computed: { @@ -46,6 +47,17 @@ const App = { }, methods: { /** + * Handle a keyup event (implements keyboard shortcuts) + * @param {object} e - The event args + */ + keyup: function(e) { + if (e.key === "Escape") { + if (this.infoVisible) this.infoVisible = false; + else window.location.href = "../"; + } + }, + + /** * Toggle whether the simulation is active */ toggle: function() { @@ -68,6 +80,14 @@ const App = { this.time += this.refreshRate; }, }, + created: function() { + // Add keyup handler + window.addEventListener("keyup", this.keyup); + }, + destroyed: function() { + // Remove keyup handler + window.removeEventListener("keyup", this.keyup); + }, } @@ -81,4 +101,5 @@ function createApp() { document.getElementById("input").hidden = false; document.getElementById("output").hidden = false; document.getElementById("data").hidden = false; + document.getElementById("info").hidden = false; } diff --git a/simulations/spring-mass-system.html b/simulations/spring-mass-system.html @@ -15,6 +15,8 @@ <div id="app"> <header> <a title="Home" href="../" class="icon"><img alt="" src="../images/home.svg"></a> + <button v-show="!infoVisible" title="About" class="icon" @click="infoVisible=true"><img alt="" src="../images/info.svg"></button> + <button v-show="infoVisible" title="Close" class="icon" @click="infoVisible=false"><img alt="" src="../images/x.svg"></button> <h1>Spring-Mass System</h1> </header> @@ -22,54 +24,91 @@ <p>This simulation requires JavaScript</p> </noscript> - <div id="input" hidden> - <section> - <label for="massInput"><b>Mass:</b> {{ mass.toFixed(1) }} Kg</label> - <input type="range" min="0.1" max="10" step="0.1" v-model.number="mass" @input="reset" @dblclick="mass=5" :disabled="active" id="massInput"> - </section> - <section> - <label for="initialPositionInput"><b>Initial Position:</b> {{ initialPosition.toFixed(1) }} m</label> - <input type="range" min="-10" max="10" step="0.1" v-model.number="initialPosition" @input="reset" @dblclick="initialPosition=0" :disabled="active" id="initialPositionInput"> - </section> - <section> - <label for="stiffnessInput"><b>Stiffness (k):</b> {{ stiffness.toFixed(0) }} N/m</label> - <input type="range" min="1" max="100" step="1" v-model.number="stiffness" @input="reset" @dblclick="stiffness=5" :disabled="active" id="stiffnessInput"> - </section> - </div> - - <div id="output" hidden> - <div id="simControls"> - <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')"> - <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> - </button> - <button @click="update" class="icon" title="Step Forward" :disabled="active"> - <img alt="" src="../images/step-forward.svg"> - </button> - <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> - <img alt="" src="../images/reset.svg"> - </button> + <div id="simulation" v-show="!infoVisible"> + <div id="input" hidden> + <section> + <label for="massInput"><b>Mass:</b> {{ mass.toFixed(1) }} Kg</label> + <input type="range" min="0.1" max="10" step="0.1" v-model.number="mass" @input="reset" @dblclick="mass=5" :disabled="active" id="massInput"> + </section> + <section> + <label for="initialPositionInput"><b>Initial Position:</b> {{ initialPosition.toFixed(1) }} m</label> + <input type="range" min="-10" max="10" step="0.1" v-model.number="initialPosition" @input="reset" @dblclick="initialPosition=0" :disabled="active" id="initialPositionInput"> + </section> + <section> + <label for="stiffnessInput"><b>Stiffness (k):</b> {{ stiffness.toFixed(0) }} N/m</label> + <input type="range" min="1" max="100" step="1" v-model.number="stiffness" @input="reset" @dblclick="stiffness=5" :disabled="active" id="stiffnessInput"> + </section> </div> - <svg width="800px" viewBox="-11 0 22 4"> - <!-- Point of equilibrium --> - <line x1="0" y1="0" :x2="0" y2="4" stroke="#808080" stroke-width="0.05" stroke-dasharray="0.1,0.1"></line> - <!-- Spring --> - <line x1="-11" y1="2" :x2="position" y2="2" stroke="#404040" stroke-width="0.1"></line> - <rect x="-11" y="1" width="0.25" height="2" fill="#000000"></rect> + <div id="output" hidden> + <div id="simControls"> + <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')"> + <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> + </button> + <button @click="update" class="icon" title="Step Forward" :disabled="active"> + <img alt="" src="../images/step-forward.svg"> + </button> + <button @click="reset" class="icon" title="Reset" :disabled="time === 0"> + <img alt="" src="../images/reset.svg"> + </button> + </div> + <svg width="800px" viewBox="-11 0 22 4"> + <!-- Point of equilibrium --> + <line x1="0" y1="0" :x2="0" y2="4" stroke="#808080" stroke-width="0.05" stroke-dasharray="0.1,0.1"></line> + + <!-- Spring --> + <line x1="-11" y1="2" :x2="position" y2="2" stroke="#404040" stroke-width="0.1"></line> + <rect x="-11" y="1" width="0.25" height="2" fill="#000000"></rect> + + <!-- Mass --> + <circle :cx="position" cy="2" :r="0.05*mass+0.1" fill="#ff0000"></circle> + </svg> + </div> - <!-- Mass --> - <circle :cx="position" cy="2" :r="0.05*mass+0.1" fill="#ff0000"></circle> - </svg> + <div id="data" hidden> + <label><b>Time:</b> {{ time.toFixed(2) }} s</label> + <label><b>Period:</b> {{ period.toFixed(2) }} s</label> + <label><b>Amplitude:</b> {{ Math.abs(initialPosition) }} m</label> + <label><b>Position:</b> {{ position.toFixed(2) }} m</label> + <label><b>Velocity:</b> {{ velocity.toFixed(2) }} m/s</label> + <label><b>Force:</b> {{ force.toFixed(2) }} N</label> + <label><b>Acceleration:</b> {{ acceleration.toFixed(2) }} m/s<sup>2</sup></label> + </div> </div> - <div id="data" hidden> - <label><b>Time:</b> {{ time.toFixed(2) }} s</label> - <label><b>Period:</b> {{ period.toFixed(2) }} s</label> - <label><b>Amplitude:</b> {{ Math.abs(initialPosition) }} m</label> - <label><b>Position:</b> {{ position.toFixed(2) }} m</label> - <label><b>Velocity:</b> {{ velocity.toFixed(2) }} m/s</label> - <label><b>Force:</b> {{ force.toFixed(2) }} N</label> - <label><b>Acceleration:</b> {{ acceleration.toFixed(2) }} m/s<sup>2</sup></label> + <div id="info" v-show="infoVisible" hidden> + <h2>Simulation Information</h2> + <p>This simulation consists of an object attached to a spring that can stretch and contract.</p> + + <p>You can control the simulation using these variables:</p> + <ul> + <li><b>Mass:</b> The mass of the object</li> + <li><b>Initial Position:</b> The initial displacement of the object relative to the point of equilibrium</li> + <li><b>Stiffness:</b> The stiffness coefficient of the spring</li> + </ul> + + <p>The simulation also contains these output measurements:</p> + <ul> + <li><b>Time:</b> The time that has passed</li> + <li><b>Period:</b> The period of the spring-mass system</li> + <li><b>Amplitude:</b> The maximum displacement of the object relative to the point of equilibrium</li> + <li><b>Position:</b> The current displacement of the object relative to the point of equilibrium</li> + <li><b>Velocity:</b> The current velocity of the object</li> + <li><b>Force:</b> The force currently being exerted on the object by the spring</li> + <li><b>Acceleration:</b> The current acceleration of the object</li> + </ul> + + <p>Controls:</p> + <ul> + <li>Use the start <img alt="" src="../images/play.svg"> and stop <img alt="" src="../images/pause.svg"> buttons to start and stop the simulation</li> + <li>Use the step <img alt="" src="../images/step-forward.svg"> button to advance the simulation by 0.01 seconds</li> + <li>Use the reset <img alt="" src="../images/reset.svg"> button to restore the simulation to its initial state</li> + </ul> + + <p>Notes:</p> + <ul> + <li>The dotted line represents the point of equilibrium.</li> + </ul> </div> </div> </body> diff --git a/simulations/spring-mass-system.js b/simulations/spring-mass-system.js @@ -8,6 +8,7 @@ const App = { active: false, // Whether the simulation is active refreshRate: 0.01, // The simulation refresh rate (s) intervalId: null, // The value returned by setInterval + infoVisible: false, } }, computed: { @@ -51,6 +52,17 @@ const App = { }, methods: { /** + * Handle a keyup event (implements keyboard shortcuts) + * @param {object} e - The event args + */ + keyup: function(e) { + if (e.key === "Escape") { + if (this.infoVisible) this.infoVisible = false; + else window.location.href = "../"; + } + }, + + /** * Toggle whether the simulation is active */ toggle: function() { @@ -73,6 +85,14 @@ const App = { this.time += this.refreshRate; }, }, + created: function() { + // Add keyup handler + window.addEventListener("keyup", this.keyup); + }, + destroyed: function() { + // Remove keyup handler + window.removeEventListener("keyup", this.keyup); + }, } @@ -86,4 +106,5 @@ function createApp() { document.getElementById("input").hidden = false; document.getElementById("output").hidden = false; document.getElementById("data").hidden = false; + document.getElementById("info").hidden = false; } diff --git a/simulations/styles.css b/simulations/styles.css @@ -9,9 +9,6 @@ div[hidden] { } #app { - display: flex; - flex-direction: column; - align-items: center; touch-action: manipulation; } @@ -29,6 +26,10 @@ header a { position: absolute; left: 5px; } +header button { + position: absolute; + right: 5px; +} noscript { color: #ff0000; @@ -48,6 +49,12 @@ noscript { cursor: default; } +#simulation { + display: flex; + flex-direction: column; + align-items: center; +} + #input, #data { display: flex; flex-direction: row; @@ -100,4 +107,31 @@ noscript { position: static; float: left; } + header button.icon { + position: static; + float: right; + } +} + +#info { + max-width: 750px; + margin: auto; + padding: 15px; + padding-top: 0px; +} +#info h2 { + text-align: center; + margin-bottom: 5px; +} +#info>* { + margin-bottom: 15px; +} +#info p + ul { + margin-top: -15px; +} +#info li { + margin-left: 30px; +} +#info img { + vertical-align: middle; }