physics-simulations

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

commit 9879bf0f9746feaed7498528b206bdb6a36ea947
parent c7e7481f439a745dc4ea663066ba974a640f758d
Author: AsherMorgan <59518073+AsherMorgan@users.noreply.github.com>
Date:   Sun, 28 Mar 2021 15:54:27 -0700

Add ability to zoom and pan projectile motion svg

Diffstat:
Aimages/zoom-in.svg | 2++
Aimages/zoom-out.svg | 2++
Msimulations/atwood-machine.html | 2+-
Msimulations/circular-motion.html | 2+-
Msimulations/horizontal-motion.html | 2+-
Msimulations/projectile-motion.html | 23++++++++++++++++++-----
Msimulations/projectile-motion.js | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msimulations/styles.css | 10++++++++--
8 files changed, 106 insertions(+), 11 deletions(-)

diff --git a/images/zoom-in.svg b/images/zoom-in.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-zoom-in"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg> +\ No newline at end of file diff --git a/images/zoom-out.svg b/images/zoom-out.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-zoom-out"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg> +\ No newline at end of file diff --git a/simulations/atwood-machine.html b/simulations/atwood-machine.html @@ -42,7 +42,7 @@ </div> <div id="output" hidden> - <div id="controls"> + <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> diff --git a/simulations/circular-motion.html b/simulations/circular-motion.html @@ -38,7 +38,7 @@ </div> <div id="output" hidden> - <div id="controls"> + <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> diff --git a/simulations/horizontal-motion.html b/simulations/horizontal-motion.html @@ -46,7 +46,7 @@ </div> <div id="output" hidden> - <div id="controls"> + <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> diff --git a/simulations/projectile-motion.html b/simulations/projectile-motion.html @@ -8,6 +8,8 @@ <link rel="icon" type="image/png" href="../images/favicon-32.png"> <link rel="apple-touch-icon" href="../images/favicon-180.png"> <script src="https://cdn.jsdelivr.net/npm/vue@3.0.6"></script> + <script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"></script> + <script src="https://hammerjs.github.io/dist/hammer.min.js"></script> <link rel="stylesheet" href="styles.css"> <script src="projectile-motion.js"></script> </head> @@ -42,7 +44,7 @@ </div> <div id="output" hidden> - <div id="controls"> + <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> @@ -53,15 +55,26 @@ <img alt="" src="../images/reset.svg"> </button> </div> - <svg width="400px" viewBox="-0.1 -0.1 10.1 10.2"> + <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="-0.1" y1="10.1" x2="10" y2="10.1" stroke-width="0.1" stroke="#404040"></line> + <line x1="-10000" y1="101" x2="10000" y2="101" stroke-width="1" stroke="#404040"></line> <!-- Path --> - <circle v-for="position in positions" :cx="position.x" :cy="10 - position.y" r="0.05" fill="#808080"></circle> + <circle v-for="position in positions" :cx="10*position.x" :cy="100 - 10*position.y" r="0.5" fill="#808080"></circle> <!-- Projectile --> - <circle :cx="position.x" :cy="10 - position.y" r="0.1" fill="#ff0000"></circle> + <circle :cx="10*position.x" :cy="100 - 10*position.y" r="1" fill="#ff0000"></circle> </svg> </div> 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 + svg: null, } }, computed: { @@ -105,10 +106,81 @@ const App = { // Create Vue app function createApp() { // Create app - Vue.createApp(App).mount("#app"); + let app = Vue.createApp(App).mount("#app"); // Unhide app divs document.getElementById("input").hidden = false; document.getElementById("output").hidden = false; document.getElementById("data").hidden = false; + + // Enable zooming + let mobileZoomPanHandler = { + haltEventListeners: ["touchstart", "touchend", "touchmove", "touchleave", "touchcancel"], + init: function(options) { + var instance = options.instance; + let initialScale = 1; + let pannedX = 0; + let pannedY = 0; + + // Init Hammer + // Listen only for pointer and touch events + this.hammer = Hammer(options.svgElement, { + inputClass: Hammer.SUPPORT_POINTER_EVENTS ? Hammer.PointerEventInput : Hammer.TouchInput + }); + + // Enable pinch + this.hammer.get("pinch").set({enable: true}); + + // Handle double tap + this.hammer.on("doubletap", function(ev){ + instance.zoomIn() + }); + + // Handle pan + this.hammer.on("panstart panmove", function(ev){ + // On pan start reset panned variables + if (ev.type === "panstart") { + pannedX = 0; + pannedY = 0; + } + + // Pan only the difference + instance.panBy({x: ev.deltaX - pannedX, y: ev.deltaY - pannedY}); + pannedX = ev.deltaX; + pannedY = ev.deltaY; + }) + + // Handle pinch + this.hammer.on("pinchstart pinchmove", function(ev) { + // Calculate true zoom center + const el = ev.target; + const rect = el.getBoundingClientRect(); + const pos = { + x: (ev.center.x - rect.left), + y: (ev.center.y - rect.top), + }; + + // On pinch start remember initial zoom + if (ev.type === "pinchstart") { + initialScale = instance.getZoom(); + instance.zoomAtPoint(initialScale * ev.scale, {x: pos.x, y: pos.y}); + } + + instance.zoomAtPoint(initialScale * ev.scale, {x: pos.x, y: pos.y}); + }); + + // Prevent moving the page on some devices when panning over SVG + options.svgElement.addEventListener("touchmove", function(e){ e.preventDefault(); }); + }, + + destroy: function(){ + this.hammer.destroy(); + } + } + app.svg = svgPanZoom("svg", { + dblClickZoomEnabled: false, + minZoom: 0.1, + maxZoom: 10, + customEventsHandler: mobileZoomPanHandler, + }); } diff --git a/simulations/styles.css b/simulations/styles.css @@ -72,12 +72,18 @@ noscript { width: min-content; border: 1px solid #000000; } -#output>#controls { +#output>#simControls { position: absolute; right: 0px; padding: 5px; padding-bottom: 0px; } +#output>#zoomControls { + position: absolute; + left: 0px; + padding: 5px; + padding-bottom: 0px; +} @media only screen and (max-width: 800px) { #output { width: calc(100% - 20px); @@ -85,7 +91,7 @@ noscript { #output>svg { width: 100%; } - #controls .icon { + #output .icon { padding: 10px; } }