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

1 line
3.1 KiB
JSON

{"name":"ScrambleText","title":"ScrambleText","description":"Detects cursor position and applies a distortion effect to text.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, watch, useTemplateRef } from 'vue';\nimport { gsap } from 'gsap';\nimport { SplitText } from 'gsap/SplitText';\nimport { ScrambleTextPlugin } from 'gsap/ScrambleTextPlugin';\n\ngsap.registerPlugin(SplitText, ScrambleTextPlugin);\n\ninterface ScrambleTextProps {\n radius?: number;\n duration?: number;\n speed?: number;\n scrambleChars?: string;\n className?: string;\n style?: Record<string, string | number>;\n}\n\nconst props = withDefaults(defineProps<ScrambleTextProps>(), {\n radius: 100,\n duration: 1.2,\n speed: 0.5,\n scrambleChars: '.:',\n className: '',\n style: () => ({})\n});\n\nconst rootRef = useTemplateRef<HTMLDivElement>('rootRef');\n\nlet splitText: SplitText | null = null;\nlet handleMove: ((e: PointerEvent) => void) | null = null;\n\nconst initializeScrambleText = () => {\n if (!rootRef.value) return;\n\n const pElement = rootRef.value.querySelector('p');\n if (!pElement) return;\n\n splitText = new SplitText(pElement, {\n type: 'chars',\n charsClass: 'inline-block will-change-transform'\n });\n\n splitText.chars.forEach(el => {\n const c = el as HTMLElement;\n gsap.set(c, { attr: { 'data-content': c.innerHTML } });\n });\n\n handleMove = (e: PointerEvent) => {\n if (!splitText) return;\n\n splitText.chars.forEach(el => {\n const c = el as HTMLElement;\n const { left, top, width, height } = c.getBoundingClientRect();\n const dx = e.clientX - (left + width / 2);\n const dy = e.clientY - (top + height / 2);\n const dist = Math.hypot(dx, dy);\n\n if (dist < props.radius) {\n gsap.to(c, {\n overwrite: true,\n duration: props.duration * (1 - dist / props.radius),\n scrambleText: {\n text: c.dataset.content || '',\n chars: props.scrambleChars,\n speed: props.speed\n },\n ease: 'none'\n });\n }\n });\n };\n\n rootRef.value.addEventListener('pointermove', handleMove);\n};\n\nconst cleanup = () => {\n if (rootRef.value && handleMove) {\n rootRef.value.removeEventListener('pointermove', handleMove);\n }\n if (splitText) {\n splitText.revert();\n splitText = null;\n }\n handleMove = null;\n};\n\nonMounted(() => {\n initializeScrambleText();\n});\n\nonUnmounted(() => {\n cleanup();\n});\n\nwatch([() => props.radius, () => props.duration, () => props.speed, () => props.scrambleChars], () => {\n cleanup();\n initializeScrambleText();\n});\n</script>\n\n<template>\n <div ref=\"rootRef\" :class=\"`scramble-text ${className}`\" :style=\"style\">\n <p>\n <slot></slot>\n </p>\n </div>\n</template>\n","path":"ScrambleText/ScrambleText.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["TextAnimations"]}