This commit is contained in:
David Haz
2025-07-12 20:14:41 +03:00
parent af10b2cfd6
commit fea147fef2
6 changed files with 414 additions and 452 deletions

View File

@@ -1,59 +1,59 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { gsap } from 'gsap'
import { SplitText } from 'gsap/SplitText'
import { ScrambleTextPlugin } from 'gsap/ScrambleTextPlugin'
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { gsap } from 'gsap';
import { SplitText } from 'gsap/SplitText';
import { ScrambleTextPlugin } from 'gsap/ScrambleTextPlugin';
gsap.registerPlugin(SplitText, ScrambleTextPlugin)
gsap.registerPlugin(SplitText, ScrambleTextPlugin);
interface ScrambleTextProps {
radius?: number
duration?: number
speed?: number
scrambleChars?: string
className?: string
style?: Record<string, string | number>
radius?: number;
duration?: number;
speed?: number;
scrambleChars?: string;
className?: string;
style?: Record<string, string | number>;
}
const props = withDefaults(defineProps<ScrambleTextProps>(), {
radius: 100,
duration: 1.2,
speed: 0.5,
scrambleChars: ".:",
className: "",
scrambleChars: '.:',
className: '',
style: () => ({})
})
});
const rootRef = ref<HTMLDivElement | null>(null)
const rootRef = ref<HTMLDivElement | null>(null);
let splitText: SplitText | null = null
let handleMove: ((e: PointerEvent) => void) | null = null
let splitText: SplitText | null = null;
let handleMove: ((e: PointerEvent) => void) | null = null;
const initializeScrambleText = () => {
if (!rootRef.value) return
if (!rootRef.value) return;
const pElement = rootRef.value.querySelector('p')
if (!pElement) return
const pElement = rootRef.value.querySelector('p');
if (!pElement) return;
splitText = new SplitText(pElement, {
type: 'chars',
charsClass: 'inline-block will-change-transform'
})
});
splitText.chars.forEach((el) => {
const c = el as HTMLElement
gsap.set(c, { attr: { 'data-content': c.innerHTML } })
})
splitText.chars.forEach(el => {
const c = el as HTMLElement;
gsap.set(c, { attr: { 'data-content': c.innerHTML } });
});
handleMove = (e: PointerEvent) => {
if (!splitText) return
splitText.chars.forEach((el) => {
const c = el as HTMLElement
const { left, top, width, height } = c.getBoundingClientRect()
const dx = e.clientX - (left + width / 2)
const dy = e.clientY - (top + height / 2)
const dist = Math.hypot(dx, dy)
if (!splitText) return;
splitText.chars.forEach(el => {
const c = el as HTMLElement;
const { left, top, width, height } = c.getBoundingClientRect();
const dx = e.clientX - (left + width / 2);
const dy = e.clientY - (top + height / 2);
const dist = Math.hypot(dx, dy);
if (dist < props.radius) {
gsap.to(c, {
@@ -65,48 +65,41 @@ const initializeScrambleText = () => {
speed: props.speed
},
ease: 'none'
})
});
}
})
}
});
};
rootRef.value.addEventListener('pointermove', handleMove)
}
rootRef.value.addEventListener('pointermove', handleMove);
};
const cleanup = () => {
if (rootRef.value && handleMove) {
rootRef.value.removeEventListener('pointermove', handleMove)
rootRef.value.removeEventListener('pointermove', handleMove);
}
if (splitText) {
splitText.revert()
splitText = null
splitText.revert();
splitText = null;
}
handleMove = null
}
handleMove = null;
};
onMounted(() => {
initializeScrambleText()
})
initializeScrambleText();
});
onUnmounted(() => {
cleanup()
})
cleanup();
});
watch(
[() => props.radius, () => props.duration, () => props.speed, () => props.scrambleChars],
() => {
cleanup()
initializeScrambleText()
}
)
watch([() => props.radius, () => props.duration, () => props.speed, () => props.scrambleChars], () => {
cleanup();
initializeScrambleText();
});
</script>
<template>
<div
ref="rootRef"
:class="`scramble-text ${className}`"
:style="style"
>
<div ref="rootRef" :class="`scramble-text ${className}`" :style="style">
<p>
<slot></slot>
</p>