export class Vec {
x: number
y: number
z: number
constructor(x: number = 0, y: number = 0, z: number = 0) {
this.x = x
this.y = y
this.z = z
}
copy(v: Vec): Vec {
this.x = v.x
this.y = v.y
this.z = v.z
return this
}
set(x: number, y: number, z: number = 0): Vec {
this.x = x
this.y = y
this.z = z
return this
}
clone(): Vec {
return new Vec(this.x, this.y, this.z)
}
add(v: Vec): Vec {
this.x += v.x
this.y += v.y
this.z += v.z
return this
}
sub(v: Vec): Vec {
this.x -= v.x
this.y -= v.y
this.z -= v.z
return this
}
mul(v: Vec): Vec {
this.x *= v.x
this.y *= v.y
this.z *= v.z
return this
}
div(v: Vec): Vec {
this.x /= v.x
this.y /= v.y
this.z /= v.z
return this
}
scale(scl: number): Vec {
this.x *= scl
this.y *= scl
this.z *= scl
return this
}
length(): number {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z)
}
lerp(target: Vec, alpha: number): Vec {
this.x = MathUtils.lerp(this.x, target.x, alpha)
this.y = MathUtils.lerp(this.y, target.y, alpha)
this.z = MathUtils.lerp(this.z, target.z, alpha)
return this
}
equals(v: Vec): boolean {
return this.x === v.x && this.y === v.y && this.z === v.z
}
dot(v: Vec): number {
return this.x * v.x + this.y * v.y + this.z * v.z
}
distanceTo(v: Vec): Vec {
return this.clone().sub(v).length()
}
normalize(): Vec {
const L = this.length()
if (L === 0) return this
this.x /= L
this.y /= L
this.z /= L
return this
}
}
<script setup lang="ts">
// You can place the class in ~/utils/math/vec.ts and import it here.
import { Vec } from '~/utils/math/vec'
const pointer = ref(new Vec())
const target = new Vec()
let frame: number | null = null
const animate = () => {
pointer.value.lerp(target, 0.15)
frame = requestAnimationFrame(animate)
}
const handlePointer = (event: PointerEvent) => {
target.set(event.clientX, event.clientY)
}
const styleCursor = computed(() => ({
transform: `translate3d(${pointer.value.x}px, ${pointer.value.y}px, 0)`
}))
onMounted(() => {
window.addEventListener('pointermove', handlePointer)
frame = requestAnimationFrame(animate)
})
onUnmounted(() => {
window.removeEventListener('pointermove', handlePointer)
if (frame) cancelAnimationFrame(frame)
})
</script>
<template>
<div class="cursor" :style="styleCursor">Vec</div>
</template>
<style scoped>
.cursor {
position: fixed;
inset: 0;
width: 60px;
aspect-ratio: 1;
display: grid;
place-items: center;
background: rgba(37, 99, 235, 0.2);
border: 1px solid #2563eb;
border-radius: 999px;
pointer-events: none;
font-weight: 600;
}
</style>