<main>
<canvas id="canvas">
<p>Your browser does not support the HTML5 canvas tag.</p>
</canvas>
<div>
<input type="number" name="angle" min="0" max="90" placeholder="Angle (deg)" />
<input type="number" name="velocity" min="0" placeholder="Velocity (m/s)" />
</div>
<div>
<button data-start>Start</button>
<button data-reset>Reset</button>
</div>
<p data-results>
<span data-time></span>
<span data-height></span>
<span data-distance></span>
</p>
</main>
@import url("https://fonts.googleapis.com/css2?family=Poppins&display=swap");
:root {
--f-family: "Poppins", sans-serif;
--f-size: 16px;
--c-primary: #0d6efd;
--c-primary-light: #3d8bfb;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: var(--f-family);
font-size: var(--f-size);
}
html,
body {
height: 100%;
display: grid;
place-items: center;
}
main {
display: flex;
gap: 1rem;
flex-direction: column;
align-items: flex-start;
}
@media screen and (max-width: 768px) {
main {
max-width: 80vw;
align-items: center;
}
}
[data-result]:empty {
display: none;
}
canvas {
border: 2px solid transparent;
border-left-color: #000;
border-bottom-color: #000;
background-image: url("data:image/svg+xml,%0A%3Csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cpattern id='smallGrid' width='8' height='8' patternUnits='userSpaceOnUse'%3E%3Cpath d='M 8 0 L 0 0 0 8' fill='none' stroke='gray' stroke-width='0.5' /%3E%3C/pattern%3E%3Cpattern id='grid' width='80' height='80' patternUnits='userSpaceOnUse'%3E%3Crect width='80' height='80' fill='url(%23smallGrid)' /%3E%3Cpath d='M 80 0 L 0 0 0 80' fill='none' stroke='gray' stroke-width='1' /%3E%3C/pattern%3E%3C/defs%3E%3Crect width='100%25' height='100%25' fill='url(%23smallGrid)' /%3E%3C/svg%3E%0A");
}
main > div {
display: flex;
gap: 10px;
}
input {
width: 100%;
padding: 10px;
outline: none;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
width: 100%;
border: none;
outline: none;
color: #fff;
padding: 10px 20px;
border-radius: 5px;
background-color: var(--c-primary);
cursor: pointer;
}
button:hover {
background-color: var(--c-primary-light);
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
[data-results] {
display: flex;
gap: 10px;
}
[data-results] span:not(:last-child) {
padding-right: 10px;
border-right: 1px solid #ccc;
}
/*******************************************
* Utilities *
*******************************************/
const toRad = (deg) => {
return (deg * Math.PI) / 180;
};
const randomColor = () => {
const MAX_COLORS = 16777215;
return `#${Math.floor(Math.random() * MAX_COLORS).toString(16)}`;
};
const drawPoint = (ctx, x, y, color = "#dc3545") => {
ctx.beginPath();
ctx.arc(x, y, 1, 0, 2 * Math.PI);
ctx.fillStyle = color;
ctx.fill();
};
/*******************************************
* Projectile *
*******************************************/
const GRAVITY = 9.8;
const getFlightTime = (velocity, rad) => {
return (2 * velocity * Math.sin(rad)) / GRAVITY;
};
const getMaxHeight = (velocity, rad) => {
return Math.pow(velocity * Math.sin(rad), 2) / (2 * GRAVITY);
};
const getDistance = (velocity, rad) => {
return (Math.pow(velocity, 2) / GRAVITY) * Math.sin(rad * 2);
};
const getPlot = (velocity, rad, time) => {
return {
x: velocity * time * Math.cos(rad),
y: velocity * time * Math.sin(rad) - (GRAVITY * Math.pow(time, 2)) / 2
};
};
/*******************************************
* Main Script *
*******************************************/
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const startButton = document.querySelector("[data-start]");
const resetButton = document.querySelector("[data-reset]");
const angleField = document.querySelector('[name="angle"]');
const velocityField = document.querySelector('[name="velocity"]');
const resultsField = document.querySelector("[data-results]");
const timeField = resultsField.querySelector("[data-time]");
const heightField = resultsField.querySelector("[data-height]");
const distanceField = resultsField.querySelector("[data-distance]");
document.addEventListener("DOMContentLoaded", () => {
startButton.addEventListener("click", start);
resetButton.addEventListener("click", reset);
window.addEventListener("resize", setCanvasSize);
setCanvasSize();
reset();
});
const start = () => {
const angle = +angleField.value;
const velocity = +velocityField.value;
if (angle > 90 || angle < 0 || velocity < 0) {
return alert("Invalid input");
}
const radial = toRad(angle);
const flightTime = getFlightTime(velocity, radial);
const maxHeight = getMaxHeight(velocity, radial);
const distance = getDistance(velocity, radial);
timeField.innerHTML = `Flight time: ${flightTime.toFixed(2)}s`;
heightField.innerHTML = `Max height: ${maxHeight.toFixed(2)}m`;
distanceField.innerHTML = `Distance: ${distance.toFixed(2)}m`;
drawProjectile(radial, velocity, flightTime);
startButton.setAttribute("disabled", true);
};
const drawProjectile = (radial, velocity, flightTime) => {
let time = 0;
const color = randomColor();
window.interval = setInterval(() => {
const { x, y } = getPlot(velocity, radial, time);
drawPoint(ctx, x, canvas.height - y, color);
time += 0.01;
if (time >= flightTime) {
clearInterval(window.interval);
startButton.removeAttribute("disabled");
}
}, 10);
};
const reset = () => {
window.clearInterval(window?.interval);
startButton.removeAttribute("disabled");
angleField.value = velocityField.value = "";
ctx.clearRect(0, 0, canvas.width, canvas.height);
timeField.innerHTML = "Flight time: 0.00s";
heightField.innerHTML = "Max height: 0.00m";
distanceField.innerHTML = "Distance: 0.00m";
};
const setCanvasSize = () => {
canvas.width = window.innerWidth * 0.8;
canvas.height = window.innerHeight / 2;
};
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.