Skip to content
On this page

Projectile motion

This example combines interaction and animation to show the freefall trajectory of a launched object. Some regular, HTML <button>s are used to start and stop the animation.

Code
vue
<script setup lang="ts">
import { Mafs, Point, Parametric, Vector, Polygon, MovablePoint, useStopwatch, useMovablePoint } from "mafs-vue"
import { computed, watch } from 'vue'

const xSpan = 1.75
const ySpan = 1.75
const initialVelocity = useMovablePoint([0.5, 1.5])
const vectorScale = 4
const g = 9.8
const xVelocity = computed(() => initialVelocity.point[0] * vectorScale)
const yVelocity = computed(() => initialVelocity.point[1] * vectorScale)
const timeOfFlight = computed(() => {
    const velocityAngle = Math.atan(yVelocity.value / xVelocity.value)
    const velocityMag = Math.sqrt(
        xVelocity.value ** 2 + yVelocity.value ** 2
    )
    return Math.abs(2 * velocityMag * Math.sin(velocityAngle)) / g
})
function positionAtTime(t: number): [number, number] {
    return [xVelocity.value * t, yVelocity.value * t - 0.5 * g * t ** 2]
}
const position = computed(() => positionAtTime(timeOfFlight.value))
const { time: t, start, stop } = useStopwatch({
    endTime: timeOfFlight
})
watch([position], () => {
    stop()
})
</script>
<template>
    <div>
        <Mafs :viewBox="{ x: [1 - xSpan, 1 + xSpan], y: [1 - ySpan, 1 + ySpan] }">
            <Polygon :points="[
                [-100, 0],
                [100, 0],
                [100, -100],
                [-100, -100],
            ]" :filled="{ color: 'green' }" />
            <Vector :tip="[
                xVelocity / vectorScale,
                yVelocity / vectorScale]" />
            <Parametric v-if="yVelocity > 0" :xy="positionAtTime" :t="[0, timeOfFlight]"
                :stroked="{ opacity: 0.4, strokeStyle: 'dashed' }" />
            <Point v-if="yVelocity > 0" :x="position[0]" :y="position[1]" :opacity="0.5" />
            <Point :x="positionAtTime(t)[0]" :y="positionAtTime(t)[1]" />
            <text :x="10" :y="30" :fontSize="20" className="transform-to-center" fill="white">
                t = {{ t.toFixed(2) }}/{{ yVelocity > 0 ? timeOfFlight.toFixed(2) : "—" }}{{ " " }}
                seconds
            </text>
            <MovablePoint :ctx="initialVelocity" />
        </Mafs>
        <div class="group">
            <button class="btn" @click="start" :disabled="yVelocity <= 0">
                Start
            </button>
            <button class="btn" @click="stop">
                Reset
            </button>
        </div>
    </div>
</template>

<style scoped>
.group {
    padding: 1rem;
    background-color: black;
    border-color: #0f172a;
}

.btn {
    border-radius: 0.125rem;
    padding-left: 1rem;
    padding-right: 1rem;
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
    margin-left: 0.5rem;
    margin-right: 0.5rem;
    background-color: #e5e7eb;
    font-weight: 700;
    color: black;
}
</style>