Files
nuxt-portfolio/app/components/GraphNode.vue
2025-12-01 16:32:21 +01:00

94 lines
2.4 KiB
Vue

<script setup lang="ts">
import {motion, frame} from "motion-v";
function getRandomArbitrary(min: number, max: number): number {
return Math.random() * (max - min) + min;
}
const {
name,
img = undefined,
color = 'bg-white',
textTop = false,
moveFactor = undefined
} = defineProps<{
name: string,
img?: string,
color?: string,
textTop?: boolean,
moveFactor?: number
}>()
const isHoveringParent = ref(false)
const roundedDivClass = `rounded-full w-full h-full absolute ${color}`
const moveFactorValue = moveFactor ? moveFactor : getRandomArbitrary(8.0, 30.0)
const spring = {damping: 5, stiffness: 50, restDelta: 0.001}
const elementRef = useTemplateRef('elementRef')
const xPoint = useMotionValue(0)
const yPoint = useMotionValue(0)
const x = useSpring(xPoint, spring)
const y = useSpring(yPoint, spring)
const handlePointerMove = ({clientX, clientY}: { clientX: number; clientY: number }) => {
const element = elementRef.value?.$el
if (!element) return
frame.read(() => {
xPoint.set((clientX - element.offsetLeft - element.offsetWidth / 2)/moveFactorValue)
yPoint.set((clientY - element.offsetTop - element.offsetHeight / 2)/moveFactorValue)
})
}
onMounted(() => {
window.addEventListener("pointermove", handlePointerMove)
})
onUnmounted(() => {
window.removeEventListener("pointermove", handlePointerMove)
})
</script>
<template>
<motion.div
ref="elementRef"
class="w-30 h-30 avatar absolute"
:style="{ x, y }"
:while-hover="{ scale: 2 }"
@hover-start="event => {isHoveringParent=true}"
@hover-end="event => {isHoveringParent=false}">
<div v-if="img !== undefined" class="mask mask-squircle w-24">
<NuxtImg :src="img"/>
</div>
<div v-else :class="roundedDivClass"/>
<motion.p
v-if="textTop"
v-show="isHoveringParent"
class="-top-8 absolute text-xs"
:initial="{ opacity: 0 }"
:animate="{ opacity: 1 }"
:exit="{ opacity: 0 }">
{{ name }}
</motion.p>
<motion.p
v-else
v-show="isHoveringParent"
class="-bottom-8 absolute text-xs"
:initial="{ opacity: 0 }"
:animate="{ opacity: 1 }"
:exit="{ opacity: 0 }">
{{ name }}
</motion.p>
</motion.div>
</template>
<style scoped>
* {
overflow: visible;
}
</style>