mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 22:49:31 -07:00
@@ -19,6 +19,7 @@ export const CATEGORIES = [
|
|||||||
'Falling Text',
|
'Falling Text',
|
||||||
'Text Cursor',
|
'Text Cursor',
|
||||||
'Decrypted Text',
|
'Decrypted Text',
|
||||||
|
'Scramble Text',
|
||||||
'True Focus',
|
'True Focus',
|
||||||
'Scroll Float',
|
'Scroll Float',
|
||||||
'Scroll Reveal',
|
'Scroll Reveal',
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const textAnimations = {
|
|||||||
'falling-text': () => import("../demo/TextAnimations/FallingTextDemo.vue"),
|
'falling-text': () => import("../demo/TextAnimations/FallingTextDemo.vue"),
|
||||||
'text-cursor': () => import("../demo/TextAnimations/TextCursorDemo.vue"),
|
'text-cursor': () => import("../demo/TextAnimations/TextCursorDemo.vue"),
|
||||||
'decrypted-text': () => import("../demo/TextAnimations/DecryptedTextDemo.vue"),
|
'decrypted-text': () => import("../demo/TextAnimations/DecryptedTextDemo.vue"),
|
||||||
|
'scramble-text': () => import("../demo/TextAnimations/ScrambleTextDemo.vue"),
|
||||||
'true-focus': () => import("../demo/TextAnimations/TrueFocusDemo.vue"),
|
'true-focus': () => import("../demo/TextAnimations/TrueFocusDemo.vue"),
|
||||||
'scroll-float': () => import("../demo/TextAnimations/ScrollFloatDemo.vue"),
|
'scroll-float': () => import("../demo/TextAnimations/ScrollFloatDemo.vue"),
|
||||||
'scroll-reveal': ()=> import("../demo/TextAnimations/ScrollRevealDemo.vue"),
|
'scroll-reveal': ()=> import("../demo/TextAnimations/ScrollRevealDemo.vue"),
|
||||||
|
|||||||
28
src/constants/code/TextAnimations/scrambleTextCode.ts
Normal file
28
src/constants/code/TextAnimations/scrambleTextCode.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import code from '@content/TextAnimations/ScrambleText/ScrambleText.vue?raw'
|
||||||
|
import type { CodeObject } from '../../../types/code'
|
||||||
|
|
||||||
|
export const scrambleTextCode: CodeObject = {
|
||||||
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/ScrambleText`,
|
||||||
|
installation: `npm install gsap`,
|
||||||
|
usage: `// Component inspired by Tom Miller from the GSAP community
|
||||||
|
// https://codepen.io/creativeocean/pen/NPWLwJM
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ScrambleText
|
||||||
|
:className="'m-[7vw] max-w-[800px] font-mono font-medium text-[clamp(14px,4vw,32px)] text-white'"
|
||||||
|
:radius="100"
|
||||||
|
:duration="1.2"
|
||||||
|
:speed="0.5"
|
||||||
|
scrambleChars=".:"
|
||||||
|
>
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipisicing elit.
|
||||||
|
Similique pariatur dignissimos porro eius quam doloremque
|
||||||
|
et enim velit nobis maxime.
|
||||||
|
</ScrambleText>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ScrambleText from "./ScrambleText.vue";
|
||||||
|
</script>`,
|
||||||
|
code
|
||||||
|
}
|
||||||
114
src/content/TextAnimations/ScrambleText/ScrambleText.vue
Normal file
114
src/content/TextAnimations/ScrambleText/ScrambleText.vue
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<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'
|
||||||
|
|
||||||
|
gsap.registerPlugin(SplitText, ScrambleTextPlugin)
|
||||||
|
|
||||||
|
interface ScrambleTextProps {
|
||||||
|
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: "",
|
||||||
|
style: () => ({})
|
||||||
|
})
|
||||||
|
|
||||||
|
const rootRef = ref<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
|
let splitText: SplitText | null = null
|
||||||
|
let handleMove: ((e: PointerEvent) => void) | null = null
|
||||||
|
|
||||||
|
const initializeScrambleText = () => {
|
||||||
|
if (!rootRef.value) 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 } })
|
||||||
|
})
|
||||||
|
|
||||||
|
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 (dist < props.radius) {
|
||||||
|
gsap.to(c, {
|
||||||
|
overwrite: true,
|
||||||
|
duration: props.duration * (1 - dist / props.radius),
|
||||||
|
scrambleText: {
|
||||||
|
text: c.dataset.content || '',
|
||||||
|
chars: props.scrambleChars,
|
||||||
|
speed: props.speed
|
||||||
|
},
|
||||||
|
ease: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rootRef.value.addEventListener('pointermove', handleMove)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
if (rootRef.value && handleMove) {
|
||||||
|
rootRef.value.removeEventListener('pointermove', handleMove)
|
||||||
|
}
|
||||||
|
if (splitText) {
|
||||||
|
splitText.revert()
|
||||||
|
splitText = null
|
||||||
|
}
|
||||||
|
handleMove = null
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initializeScrambleText()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cleanup()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => props.radius, () => props.duration, () => props.speed, () => props.scrambleChars],
|
||||||
|
() => {
|
||||||
|
cleanup()
|
||||||
|
initializeScrambleText()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="rootRef"
|
||||||
|
:class="`scramble-text ${className}`"
|
||||||
|
:style="style"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<slot></slot>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
124
src/demo/TextAnimations/ScrambleTextDemo.vue
Normal file
124
src/demo/TextAnimations/ScrambleTextDemo.vue
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import TabbedLayout from '../../components/common/TabbedLayout.vue'
|
||||||
|
import PropTable from '../../components/common/PropTable.vue'
|
||||||
|
import Dependencies from '../../components/code/Dependencies.vue'
|
||||||
|
import CliInstallation from '../../components/code/CliInstallation.vue'
|
||||||
|
import CodeExample from '../../components/code/CodeExample.vue'
|
||||||
|
import Customize from '../../components/common/Customize.vue'
|
||||||
|
import PreviewSlider from '../../components/common/PreviewSlider.vue'
|
||||||
|
import ScrambleText from '../../content/TextAnimations/ScrambleText/ScrambleText.vue'
|
||||||
|
import { scrambleTextCode } from '@/constants/code/TextAnimations/scrambleTextCode'
|
||||||
|
|
||||||
|
const radius = ref(100)
|
||||||
|
const duration = ref(1.2)
|
||||||
|
const speed = ref(0.5)
|
||||||
|
const scrambleChars = ref(".:")
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: "radius",
|
||||||
|
type: "number",
|
||||||
|
default: "100",
|
||||||
|
description: "The radius around the mouse pointer within which characters will scramble."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duration",
|
||||||
|
type: "number",
|
||||||
|
default: "1.2",
|
||||||
|
description: "The duration of the scramble effect on a character."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "speed",
|
||||||
|
type: "number",
|
||||||
|
default: "0.5",
|
||||||
|
description: "The speed of the scramble animation."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scrambleChars",
|
||||||
|
type: "string",
|
||||||
|
default: "'.:'",
|
||||||
|
description: "The characters used for scrambling."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "className",
|
||||||
|
type: "string",
|
||||||
|
default: '""',
|
||||||
|
description: "Additional CSS classes for the component."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "style",
|
||||||
|
type: "Record<string, string | number>",
|
||||||
|
default: "{}",
|
||||||
|
description: "Inline styles for the component."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="scramble-text-demo">
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="demo-container relative h-[500px] overflow-hidden">
|
||||||
|
<ScrambleText
|
||||||
|
:className="'m-[7vw] max-w-[800px] font-mono font-medium text-[clamp(14px,4vw,32px)] text-white'"
|
||||||
|
:radius="radius"
|
||||||
|
:duration="duration"
|
||||||
|
:speed="speed"
|
||||||
|
:scrambleChars="scrambleChars"
|
||||||
|
>
|
||||||
|
Once you hover over me, you will see the effect in action! You can customize the radius, duration, and speed of the scramble effect.
|
||||||
|
</ScrambleText>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">Scramble Characters</label>
|
||||||
|
<input
|
||||||
|
v-model="scrambleChars"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter text..."
|
||||||
|
maxlength="5"
|
||||||
|
class="w-[160px] px-3 py-2 bg-[#0b0b0b] border border-[#333] rounded-md text-white focus:outline-none focus:border-[#666]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Radius"
|
||||||
|
v-model="radius"
|
||||||
|
:min="10"
|
||||||
|
:max="300"
|
||||||
|
:step="10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Duration"
|
||||||
|
v-model="duration"
|
||||||
|
:min="0.1"
|
||||||
|
:max="5"
|
||||||
|
:step="0.1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Speed"
|
||||||
|
v-model="speed"
|
||||||
|
:min="0.1"
|
||||||
|
:max="2"
|
||||||
|
:step="0.1"
|
||||||
|
/>
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['gsap']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="scrambleTextCode" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="scrambleTextCode.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user