feat: graph lines and more customization available for nodes

This commit is contained in:
2025-12-02 11:46:19 +01:00
parent 97b69149ca
commit a739910315
2 changed files with 88 additions and 13 deletions

View File

@@ -1,27 +1,34 @@
<script setup lang="ts"> <script setup lang="ts">
import {motion, frame} from "motion-v"; import {motion, frame} from "motion-v";
function getRandomArbitrary(min: number, max: number): number {
return Math.random() * (max - min) + min;
}
const { const {
name, name,
img = undefined, img = undefined,
color = 'bg-white', color = 'bg-white',
textTop = false, textTop = false,
moveFactor = undefined moveFactor = undefined,
scaleFactor = 2,
initialWidth = undefined,
initialHeight = undefined,
} = defineProps<{ } = defineProps<{
name: string, name: string,
img?: string, img?: string,
color?: string, color?: string,
textTop?: boolean, textTop?: boolean,
moveFactor?: number moveFactor?: number,
scaleFactor?: number,
initialWidth?: string,
initialHeight?: string,
}>() }>()
const emit = defineEmits(['positionChanged'])
const isHoveringParent = ref(false) const isHoveringParent = ref(false)
const roundedDivClass = `rounded-full w-full h-full absolute ${color}` const roundedDivClass = `rounded-full w-full h-full absolute ${color}`
function getRandomArbitrary(min: number, max: number): number {
return Math.random() * (max - min) + min;
}
const moveFactorValue = moveFactor ? moveFactor : getRandomArbitrary(8.0, 30.0) const moveFactorValue = moveFactor ? moveFactor : getRandomArbitrary(8.0, 30.0)
const spring = {damping: 5, stiffness: 50, restDelta: 0.001} const spring = {damping: 5, stiffness: 50, restDelta: 0.001}
@@ -40,6 +47,16 @@ const handlePointerMove = ({clientX, clientY}: { clientX: number; clientY: numbe
}) })
} }
useRafFn(() => {
const element = elementRef.value?.$el
if (!element)
return
emit(
'positionChanged',
x.get() + element.offsetLeft + element.offsetWidth/2,
y.get() + element.offsetTop + element.offsetHeight/2,)
})
onMounted(() => { onMounted(() => {
window.addEventListener("pointermove", handlePointerMove) window.addEventListener("pointermove", handlePointerMove)
}) })
@@ -48,19 +65,20 @@ onUnmounted(() => {
window.removeEventListener("pointermove", handlePointerMove) window.removeEventListener("pointermove", handlePointerMove)
}) })
const baseElementClass = ref(`${initialWidth ? initialWidth : 'w-30'} ${initialHeight ? initialHeight : 'h-30'} avatar absolute`)
</script> </script>
<template> <template>
<motion.div <motion.div
ref="elementRef" ref="elementRef"
class="w-30 h-30 avatar absolute" :class="baseElementClass"
:style="{ x, y }" :style="{ x, y }"
:while-hover="{ scale: 2 }" :while-hover="{ scale: scaleFactor }"
@hover-start="event => {isHoveringParent=true}" @hover-start="event => {isHoveringParent=true}"
@hover-end="event => {isHoveringParent=false}"> @hover-end="event => {isHoveringParent=false}">
<div v-if="img !== undefined" class="mask mask-squircle w-24"> <div v-if="img !== undefined" class="mask mask-squircle w-full">
<NuxtImg :src="img"/> <NuxtImg :src="img"/>
</div> </div>
<div v-else :class="roundedDivClass"/> <div v-else :class="roundedDivClass"/>

View File

@@ -1,11 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
const nodes = [ const nodes = [
{
name: "Hi, I'm Alex",
img: "/face.jpg",
textTop: true,
moveFactor: 6,
hOffset: 'left-1/3',
vOffset: 'top-1/3',
},
{ {
name: "DevOps engineer", name: "DevOps engineer",
color: "bg-blue-100", color: "bg-blue-100",
hOffset: 'left-30', hOffset: 'left-30',
vOffset: 'top-30', vOffset: 'top-30',
initialWidth: 'w-2',
initialHeight: 'h-2',
scaleFactor: 10,
}, },
{ {
name: "Game developer", name: "Game developer",
@@ -28,20 +39,66 @@ const nodes = [
textTop: true textTop: true
}, },
] ]
const divsLocations = ref(nodes.map(() => {
return {x: 0, y: 0}
}))
const linesLocations = ref(new Array<{x1: number, y1: number, x2: number, y2: number}>())
for (let i = 0; i < divsLocations.value.length - 1; i++) {
const startLocation = divsLocations.value[i]!
const endLocation = divsLocations.value[i+1]!
linesLocations.value.push({x1: startLocation.x, y1: startLocation.y, x2: endLocation.x, y2: endLocation.y})
}
linesLocations.value.push({x1: 0, y1: 0, x2: 0, y2: 0})
const nodeMoved = (nodeIndex: number, x: number, y: number) => {
divsLocations.value[nodeIndex] = {x: x, y: y}
}
useRafFn(() => {
for (let i = 0; i < divsLocations.value.length - 1; i++) {
const startLocation = divsLocations.value[i]!
const endLocation = divsLocations.value[i+1]!
linesLocations.value[i] = {x1: startLocation.x, y1: startLocation.y, x2: endLocation.x, y2: endLocation.y}
}
linesLocations.value[linesLocations.value.length - 1] = {
x1: divsLocations.value[divsLocations.value.length - 1]!.x,
y1: divsLocations.value[divsLocations.value.length - 1]!.y,
x2: divsLocations.value[0]!.x,
y2: divsLocations.value[0]!.y
}
})
</script> </script>
<template> <template>
<div class="hero bg-base-200 min-h-screen"> <div class="hero bg-base-200 min-h-screen">
<div class="overflow-hidden"> <div class="overflow-hidden">
<GraphNode img="/face.jpg" name="Hi, I'm Alex" :text-top="true" :move-factor="6"/> <ul v-for="(line, index) in linesLocations" :key="index">
<li>
<svg height="100%" width="100%" class="absolute top-0 left-0">
<line
:x1="line.x1"
:y1="line.y1"
:x2="line.x2"
:y2="line.y2" stroke="white"/>
</svg>
</li>
</ul>
<ul v-for="(node, index) in nodes" :key="index"> <ul v-for="(node, index) in nodes" :key="index">
<li> <li>
<GraphNode <GraphNode
:name="node.name" :name="node.name"
:img="node.img"
:color="node.color" :color="node.color"
:class="`${node.hOffset} ${node.vOffset}`" :class="`${node.hOffset ? node.hOffset : ''} ${node.vOffset ? node.vOffset : ''}`"
:text-top="node.textTop"/> :text-top="node.textTop"
:move-factor="node.moveFactor"
:scale-factor="node.scaleFactor"
:initial-width="node.initialWidth"
:initial-height="node.initialHeight"
@position-changed="(x, y) => { nodeMoved(index, x, y) }"/>
</li> </li>
</ul> </ul>
</div> </div>