Files
vue-bits/public/r/MagnetLines.json
David Haz e621971723 jsrepo v3
2025-12-15 23:50:24 +02:00

1 line
3.1 KiB
JSON

{"name":"MagnetLines","title":"MagnetLines","description":"Animated field lines bend toward the cursor.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, computed, useTemplateRef } from 'vue';\n\ninterface MagnetLinesProps {\n rows?: number;\n columns?: number;\n containerSize?: string;\n lineColor?: string;\n lineWidth?: string;\n lineHeight?: string;\n baseAngle?: number;\n className?: string;\n style?: Record<string, string | number>;\n}\n\nconst props = withDefaults(defineProps<MagnetLinesProps>(), {\n rows: 9,\n columns: 9,\n containerSize: '80vmin',\n lineColor: '#efefef',\n lineWidth: '1vmin',\n lineHeight: '6vmin',\n baseAngle: -10,\n className: '',\n style: () => ({})\n});\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\n\nconst total = computed(() => props.rows * props.columns);\n\nconst onPointerMove = (pointer: { x: number; y: number }) => {\n const container = containerRef.value;\n if (!container) return;\n\n const items = container.querySelectorAll<HTMLSpanElement>('span');\n\n items.forEach(item => {\n const rect = item.getBoundingClientRect();\n const centerX = rect.x + rect.width / 2;\n const centerY = rect.y + rect.height / 2;\n\n const b = pointer.x - centerX;\n const a = pointer.y - centerY;\n const c = Math.sqrt(a * a + b * b) || 1;\n const r = ((Math.acos(b / c) * 180) / Math.PI) * (pointer.y > centerY ? 1 : -1);\n\n item.style.setProperty('--rotate', `${r}deg`);\n });\n};\n\nconst handlePointerMove = (e: PointerEvent) => {\n onPointerMove({ x: e.x, y: e.y });\n};\n\nonMounted(() => {\n const container = containerRef.value;\n if (!container) return;\n\n window.addEventListener('pointermove', handlePointerMove);\n\n const items = container.querySelectorAll<HTMLSpanElement>('span');\n if (items.length) {\n const middleIndex = Math.floor(items.length / 2);\n const rect = items[middleIndex].getBoundingClientRect();\n onPointerMove({ x: rect.x, y: rect.y });\n }\n});\n\nonUnmounted(() => {\n window.removeEventListener('pointermove', handlePointerMove);\n});\n</script>\n\n<template>\n <div\n ref=\"containerRef\"\n :class=\"`grid place-items-center ${props.className}`\"\n :style=\"{\n gridTemplateColumns: `repeat(${props.columns}, 1fr)`,\n gridTemplateRows: `repeat(${props.rows}, 1fr)`,\n width: props.containerSize,\n height: props.containerSize,\n ...props.style\n }\"\n >\n <span\n v-for=\"i in total\"\n :key=\"i\"\n class=\"block origin-center\"\n :style=\"{\n backgroundColor: props.lineColor,\n width: props.lineWidth,\n height: props.lineHeight,\n '--rotate': `${props.baseAngle}deg`,\n transform: 'rotate(var(--rotate))',\n willChange: 'transform'\n }\"\n />\n </div>\n</template>\n","path":"MagnetLines/MagnetLines.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Animations"]}