From 90a41d1469c333bd5f61a7f808160a4a98adfce5 Mon Sep 17 00:00:00 2001 From: Utkarsh-Singhal-26 Date: Sat, 12 Jul 2025 18:19:41 +0530 Subject: [PATCH 1/2] Added text animation --- src/constants/Categories.ts | 3 +- src/constants/Components.ts | 3 +- .../code/TextAnimations/rotatingTextCode.ts | 26 ++ .../RotatingText/RotatingText.vue | 233 ++++++++++++++++++ src/demo/TextAnimations/RotatingTextDemo.vue | 166 +++++++++++++ 5 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 src/constants/code/TextAnimations/rotatingTextCode.ts create mode 100644 src/content/TextAnimations/RotatingText/RotatingText.vue create mode 100644 src/demo/TextAnimations/RotatingTextDemo.vue diff --git a/src/constants/Categories.ts b/src/constants/Categories.ts index 021944a..527592d 100644 --- a/src/constants/Categories.ts +++ b/src/constants/Categories.ts @@ -21,7 +21,8 @@ export const CATEGORIES = [ 'Decrypted Text', 'True Focus', 'Scroll Float', - 'Scroll Reveal' + 'Scroll Reveal', + 'Rotating Text' ] }, { diff --git a/src/constants/Components.ts b/src/constants/Components.ts index 6f5a3cf..4641444 100644 --- a/src/constants/Components.ts +++ b/src/constants/Components.ts @@ -26,7 +26,8 @@ const textAnimations = { 'decrypted-text': () => import("../demo/TextAnimations/DecryptedTextDemo.vue"), 'true-focus': () => import("../demo/TextAnimations/TrueFocusDemo.vue"), 'scroll-float': () => import("../demo/TextAnimations/ScrollFloatDemo.vue"), - 'scroll-reveal': ()=> import("../demo/TextAnimations/ScrollRevealDemo.vue") + 'scroll-reveal': ()=> import("../demo/TextAnimations/ScrollRevealDemo.vue"), + 'rotating-text': ()=> import("../demo/TextAnimations/RotatingTextDemo.vue") }; const components = { diff --git a/src/constants/code/TextAnimations/rotatingTextCode.ts b/src/constants/code/TextAnimations/rotatingTextCode.ts new file mode 100644 index 0000000..ac8c546 --- /dev/null +++ b/src/constants/code/TextAnimations/rotatingTextCode.ts @@ -0,0 +1,26 @@ +import code from '@/content/TextAnimations/RotatingText/RotatingText.vue?raw'; +import type { CodeObject } from '../../../types/code'; + +export const rotatingText: CodeObject = { + cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/RotatingText`, + installation: `npm i motion-v`, + usage: ` + +`, + code +}; diff --git a/src/content/TextAnimations/RotatingText/RotatingText.vue b/src/content/TextAnimations/RotatingText/RotatingText.vue new file mode 100644 index 0000000..1d44010 --- /dev/null +++ b/src/content/TextAnimations/RotatingText/RotatingText.vue @@ -0,0 +1,233 @@ + + + diff --git a/src/demo/TextAnimations/RotatingTextDemo.vue b/src/demo/TextAnimations/RotatingTextDemo.vue new file mode 100644 index 0000000..5c0ff2b --- /dev/null +++ b/src/demo/TextAnimations/RotatingTextDemo.vue @@ -0,0 +1,166 @@ + + + From adfaf20b5f5d7725bc6a6453aae4c3e531d88d7d Mon Sep 17 00:00:00 2001 From: Utkarsh-Singhal-26 Date: Sat, 12 Jul 2025 18:40:23 +0530 Subject: [PATCH 2/2] Fix Smooth Transition --- .../RotatingText/RotatingText.vue | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/content/TextAnimations/RotatingText/RotatingText.vue b/src/content/TextAnimations/RotatingText/RotatingText.vue index 1d44010..ff00e25 100644 --- a/src/content/TextAnimations/RotatingText/RotatingText.vue +++ b/src/content/TextAnimations/RotatingText/RotatingText.vue @@ -46,7 +46,7 @@ const props = withDefaults(defineProps(), { }); const currentTextIndex = ref(0); -let intervalId: number | undefined = undefined; +let intervalId: number | null = null; const splitIntoCharacters = (text: string): string[] => { if (typeof Intl !== 'undefined' && Intl.Segmenter) { @@ -85,14 +85,6 @@ const elements = computed(() => { })); }); -const getPreviousCharsCount = (wordIndex: number): number => { - return elements.value.slice(0, wordIndex).reduce((sum, word) => sum + word.characters.length, 0); -}; - -const getTotalCharsCount = (): number => { - return elements.value.reduce((sum, word) => sum + word.characters.length, 0); -}; - const getStaggerDelay = (index: number, totalChars: number): number => { const total = totalChars; @@ -112,9 +104,7 @@ const getStaggerDelay = (index: number, totalChars: number): number => { const handleIndexChange = (newIndex: number) => { currentTextIndex.value = newIndex; - if (props.onNext) { - props.onNext(newIndex); - } + if (props.onNext) props.onNext(newIndex); }; const next = () => { @@ -150,7 +140,7 @@ const jumpTo = (index: number) => { const reset = () => { if (currentTextIndex.value !== 0) { - currentTextIndex.value = 0; + handleIndexChange(0); } }; @@ -162,14 +152,14 @@ defineExpose({ }); watch( - () => props.auto, - newAuto => { + () => [props.auto, props.rotationInterval], + () => { if (intervalId) { clearInterval(intervalId); - intervalId = undefined; + intervalId = null; } - if (newAuto) { + if (props.auto) { intervalId = setInterval(next, props.rotationInterval); } }, @@ -206,8 +196,8 @@ onUnmounted(() => { :key="currentTextIndex" tag="span" :class="cn(splitBy === 'lines' ? 'flex flex-col w-full' : 'flex flex-wrap whitespace-pre-wrap relative')" - layout aria-hidden="true" + layout > { :exit="exit" :transition="{ ...transition, - delay: getStaggerDelay(getPreviousCharsCount(wordIndex) + charIndex, getTotalCharsCount()) + delay: getStaggerDelay( + elements.slice(0, wordIndex).reduce((sum, word) => sum + word.characters.length, 0) + charIndex, + elements.reduce((sum, word) => sum + word.characters.length, 0) + ) }" :class="cn('inline-block', elementLevelClassName)" > {{ char }} -   +