{"id":79,"date":"2026-05-20T17:30:23","date_gmt":"2026-05-20T17:30:23","guid":{"rendered":"https:\/\/flowxiom.com\/?p=79"},"modified":"2026-05-20T17:30:24","modified_gmt":"2026-05-20T17:30:24","slug":"visual-projectile","status":"publish","type":"post","link":"https:\/\/flowxiom.com\/index.php\/2026\/05\/20\/visual-projectile\/","title":{"rendered":"Visual Projectile"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Projectile Motion Physics Simulator<\/title>\n    <style>\n        body {\n            font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;\n            background-color: #f0f2f5;\n            color: #333;\n            margin: 0;\n            padding: 20px;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n        }\n\n        .container {\n            background: white;\n            padding: 25px;\n            border-radius: 12px;\n            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);\n            max-width: 900px;\n            width: 100%;\n        }\n\n        h1 {\n            text-align: center;\n            color: #1a1a1a;\n            margin-top: 0;\n        }\n\n        canvas {\n            background: #282c34;\n            border-radius: 8px;\n            display: block;\n            margin: 0 auto 20px;\n            width: 100%;\n            max-width: 850px;\n            box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);\n        }\n\n        .panel {\n            display: grid;\n            grid-template-columns: 1fr 1fr;\n            gap: 25px;\n        }\n\n        .controls,\n        .results {\n            background: #f8f9fa;\n            padding: 20px;\n            border-radius: 8px;\n            border: 1px solid #e0e0e0;\n        }\n\n        .control-group {\n            margin-bottom: 15px;\n        }\n\n        label {\n            display: block;\n            font-weight: bold;\n            margin-bottom: 8px;\n            color: #444;\n        }\n\n        input[type=\"range\"] {\n            width: 100%;\n            cursor: pointer;\n        }\n\n        .results h3 {\n            margin-top: 0;\n            color: #0d6efd;\n            border-bottom: 2px solid #cce5ff;\n            padding-bottom: 5px;\n        }\n\n        .results p {\n            margin: 10px 0;\n            font-size: 16px;\n            display: flex;\n            justify-content: space-between;\n        }\n\n        .highlight {\n            font-weight: bold;\n            color: #d63384;\n            font-size: 1.1em;\n        }\n\n        .formula-note {\n            font-size: 0.9em;\n            color: #6c757d;\n            margin-top: 15px;\n            padding-top: 15px;\n            border-top: 1px dashed #ccc;\n        }\n    <\/style>\n<\/head>\n\n<body>\n\n    <div class=\"container\">\n        <h1>Projectile Motion Physics Demonstration<\/h1>\n\n        <canvas id=\"simCanvas\" width=\"850\" height=\"400\"><\/canvas>\n\n        <div class=\"panel\">\n            <div class=\"controls\">\n                <div class=\"control-group\">\n                    <label>Launch Height (h): <span id=\"h-val\">0.0<\/span> m<\/label>\n                    <input type=\"range\" id=\"h-slider\" min=\"0\" max=\"50\" step=\"1\" value=\"0\">\n                <\/div>\n                <div class=\"control-group\">\n                    <label>Initial Velocity (v\u2080): <span id=\"v-val\">25.0<\/span> m\/s<\/label>\n                    <input type=\"range\" id=\"v-slider\" min=\"1\" max=\"50\" step=\"1\" value=\"25\">\n                <\/div>\n                <div class=\"control-group\">\n                    <label>Launch Angle (\u03b8): <span id=\"theta-val\">45<\/span>\u00b0<\/label>\n                    <input type=\"range\" id=\"theta-slider\" min=\"0\" max=\"90\" step=\"1\" value=\"45\">\n                <\/div>\n                <div class=\"control-group\">\n                    <label>Gravity (g): 9.81 m\/s\u00b2 (Fixed)<\/label>\n                <\/div>\n            <\/div>\n\n            <div class=\"results\">\n                <h3>Real-time Trajectory Data<\/h3>\n                <p><span>Initial Horizontal Velocity (v\u2080\u2093):<\/span> <span id=\"res-vx\" class=\"highlight\">0.00 m\/s<\/span><\/p>\n                <p><span>Initial Vertical Velocity (v\u2080y):<\/span> <span id=\"res-vy\" class=\"highlight\">0.00 m\/s<\/span><\/p>\n                <p><span>Max Height (H):<\/span> <span id=\"res-hmax\" class=\"highlight\">0.00 m<\/span><\/p>\n                <p><span>Total Flight Time (T):<\/span> <span id=\"res-time\" class=\"highlight\">0.00 s<\/span><\/p>\n                <p><span>Max Range (R):<\/span> <span id=\"res-range\" class=\"highlight\">0.00 m<\/span><\/p>\n\n                <div class=\"formula-note\">\n                    <strong>Physics Laws:<\/strong> Uniform linear motion horizontally, uniform accelerated motion by gravity vertically. Canvas is rendered with 1:1 real physical aspect ratio.\n                <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <script>\n        const canvas = document.getElementById('simCanvas');\n        const ctx = canvas.getContext('2d');\n\n        \/\/ UI Elements\n        const hSlider = document.getElementById('h-slider');\n        const vSlider = document.getElementById('v-slider');\n        const thetaSlider = document.getElementById('theta-slider');\n\n        const hVal = document.getElementById('h-val');\n        const vVal = document.getElementById('v-val');\n        const thetaVal = document.getElementById('theta-val');\n\n        const resVx = document.getElementById('res-vx');\n        const resVy = document.getElementById('res-vy');\n        const resHmax = document.getElementById('res-hmax');\n        const resTime = document.getElementById('res-time');\n        const resRange = document.getElementById('res-range');\n\n        \/\/ Physics Constants\n        const g = 9.81;\n\n        \/\/ Animation State\n        let startTime = performance.now();\n        let animationId;\n\n        function getParams() {\n            const h = parseFloat(hSlider.value);\n            const v0 = parseFloat(vSlider.value);\n            const thetaDeg = parseFloat(thetaSlider.value);\n            const thetaRad = thetaDeg * Math.PI \/ 180;\n\n            return { h, v0, thetaDeg, thetaRad };\n        }\n\n        function calculatePhysics() {\n            const { h, v0, thetaRad } = getParams();\n\n            \/\/ Resolve velocity\n            const v0x = v0 * Math.cos(thetaRad);\n            const v0y = v0 * Math.sin(thetaRad);\n\n            \/\/ Flight time: solve quadratic equation -0.5*g*t^2 + v0y*t + h = 0\n            const t_flight = (v0y + Math.sqrt(v0y * v0y + 2 * g * h)) \/ g;\n\n            \/\/ Horizontal range\n            const range = v0x * t_flight;\n\n            \/\/ Max height: t_peak = v0y \/ g\n            const t_peak = v0y \/ g;\n            const h_max = h + (v0y * v0y) \/ (2 * g);\n\n            return { v0x, v0y, t_flight, range, h_max };\n        }\n\n        function updateUI() {\n            const p = getParams();\n            const r = calculatePhysics();\n\n            hVal.textContent = p.h.toFixed(1);\n            vVal.textContent = p.v0.toFixed(1);\n            thetaVal.textContent = p.thetaDeg;\n\n            resVx.textContent = r.v0x.toFixed(2) + ' m\/s';\n            resVy.textContent = r.v0y.toFixed(2) + ' m\/s';\n            resHmax.textContent = r.h_max.toFixed(2) + ' m';\n            resTime.textContent = r.t_flight.toFixed(2) + ' s';\n            resRange.textContent = r.range.toFixed(2) + ' m';\n        }\n\n        \/\/ Coordinate system transform: Physical (m) -> Canvas (px)\n        function draw() {\n            const params = getParams();\n            const physics = calculatePhysics();\n\n            ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n            \/\/ Padding\n            const padX = 50;\n            const padY = 50;\n            const drawW = canvas.width - padX * 2;\n            const drawH = canvas.height - padY * 2;\n\n            \/\/ Dynamic scale to maintain 1:1 physical aspect ratio, prevent trajectory distortion\n            \/\/ We need to fit max X (range) and max Y (height)\n            const maxPhysX = Math.max(10, physics.range);\n            const maxPhysY = Math.max(10, physics.h_max);\n\n            const scale = Math.min(drawW \/ maxPhysX, drawH \/ maxPhysY);\n\n            \/\/ Origin at bottom left\n            const originX = padX;\n            const originY = canvas.height - padY;\n\n            function toPx(physX, physY) {\n                return {\n                    x: originX + physX * scale,\n                    y: originY - physY * scale\n                };\n            }\n\n            \/\/ 1. Draw grid lines and coordinate axes\n            ctx.strokeStyle = '#3e4451';\n            ctx.lineWidth = 1;\n            ctx.beginPath();\n            \/\/ Ground\n            ctx.moveTo(0, originY);\n            ctx.lineTo(canvas.width, originY);\n            \/\/ Y-axis\n            ctx.moveTo(originX, canvas.height);\n            ctx.lineTo(originX, 0);\n            ctx.stroke();\n\n            \/\/ 2. Draw parabolic trajectory (dashed)\n            ctx.beginPath();\n            ctx.strokeStyle = '#61afef';\n            ctx.lineWidth = 2;\n            ctx.setLineDash([5, 5]);\n\n            for (let t = 0; t <= physics.t_flight; t += 0.05) {\n                const x = physics.v0x * t;\n                const y = params.h + physics.v0y * t - 0.5 * g * t * t;\n                const pos = toPx(x, y);\n                if (t === 0) ctx.moveTo(pos.x, pos.y);\n                else ctx.lineTo(pos.x, pos.y);\n            }\n            \/\/ Ensure connection to landing point\n            const finalPos = toPx(physics.range, 0);\n            ctx.lineTo(finalPos.x, finalPos.y);\n            ctx.stroke();\n            ctx.setLineDash([]);\n\n            \/\/ 3. Calculate current position of animated ball\n            const now = performance.now();\n            \/\/ Cycle = flight time + 0.5s pause\n            const cycleTime = physics.t_flight + 0.5;\n            const elapsedSec = ((now - startTime) \/ 1000) % cycleTime;\n\n            let currentT = elapsedSec;\n            if (currentT > physics.t_flight) currentT = physics.t_flight; \/\/ Stop at ground\n\n            const currentX = physics.v0x * currentT;\n            const currentY = params.h + physics.v0y * currentT - 0.5 * g * currentT * currentT;\n            const ballPos = toPx(currentX, currentY);\n\n            \/\/ 4. Draw launch pad (if height > 0)\n            if (params.h > 0) {\n                ctx.fillStyle = '#98c379';\n                const base = toPx(0, params.h);\n                ctx.fillRect(originX - 10, base.y, 20, params.h * scale);\n            }\n\n            \/\/ 5. Draw animated ball\n            ctx.beginPath();\n            ctx.arc(ballPos.x, ballPos.y, 8, 0, Math.PI * 2);\n            ctx.fillStyle = '#e06c75';\n            ctx.fill();\n            ctx.strokeStyle = '#fff';\n            ctx.lineWidth = 2;\n            ctx.stroke();\n\n            \/\/ Draw velocity vector arrow (optional visual enhancement)\n            if (currentT < physics.t_flight) {\n                const vx = physics.v0x;\n                const vy = physics.v0y - g * currentT;\n                ctx.beginPath();\n                ctx.moveTo(ballPos.x, ballPos.y);\n                \/\/ Scale down arrow length proportionally to avoid being too long\n                ctx.lineTo(ballPos.x + vx * scale * 0.2, ballPos.y - vy * scale * 0.2);\n                ctx.strokeStyle = '#abb2bf';\n                ctx.lineWidth = 2;\n                ctx.stroke();\n            }\n\n            animationId = requestAnimationFrame(draw);\n        }\n\n        \/\/ Listen for slider changes\n        function onInput() {\n            updateUI();\n            \/\/ Reset animation time so the ball flies from the beginning\n            startTime = performance.now();\n        }\n\n        hSlider.addEventListener('input', onInput);\n        vSlider.addEventListener('input', onInput);\n        thetaSlider.addEventListener('input', onInput);\n\n        \/\/ Initialization\n        updateUI();\n        draw();\n    <\/script>\n\n<\/body>\n\n<\/html>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Launch, adjust angles, and track trajectories in real time to master the elements of projectile motion.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[6],"tags":[],"class_list":["post-79","post","type-post","status-publish","format-standard","hentry","category-visual-learning-tools"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/flowxiom.com\/index.php\/wp-json\/wp\/v2\/posts\/79","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/flowxiom.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/flowxiom.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/flowxiom.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/flowxiom.com\/index.php\/wp-json\/wp\/v2\/comments?post=79"}],"version-history":[{"count":1,"href":"https:\/\/flowxiom.com\/index.php\/wp-json\/wp\/v2\/posts\/79\/revisions"}],"predecessor-version":[{"id":80,"href":"https:\/\/flowxiom.com\/index.php\/wp-json\/wp\/v2\/posts\/79\/revisions\/80"}],"wp:attachment":[{"href":"https:\/\/flowxiom.com\/index.php\/wp-json\/wp\/v2\/media?parent=79"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/flowxiom.com\/index.php\/wp-json\/wp\/v2\/categories?post=79"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/flowxiom.com\/index.php\/wp-json\/wp\/v2\/tags?post=79"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}