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:
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 <= 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;
}
}