diff --git a/src/components/landing/FeatureCards/FeatureCards.vue b/src/components/landing/FeatureCards/FeatureCards.vue
index 17a270b..bd22b2a 100644
--- a/src/components/landing/FeatureCards/FeatureCards.vue
+++ b/src/components/landing/FeatureCards/FeatureCards.vue
@@ -41,7 +41,7 @@
`
});
diff --git a/src/constants/code/Animations/countUpCode.ts b/src/constants/code/TextAnimations/countUpCode.ts
similarity index 79%
rename from src/constants/code/Animations/countUpCode.ts
rename to src/constants/code/TextAnimations/countUpCode.ts
index 6e443ec..ce8b47e 100644
--- a/src/constants/code/Animations/countUpCode.ts
+++ b/src/constants/code/TextAnimations/countUpCode.ts
@@ -1,7 +1,7 @@
-import code from '@/content/Animations/CountUp/CountUp.vue?raw';
+import code from '@/content/TextAnimations/CountUp/CountUp.vue?raw';
import { createCodeObject } from '@/types/code';
-export const countup = createCodeObject(code, 'Animations/CountUp', {
+export const countup = createCodeObject(code, 'TextAnimations/CountUp', {
usage: `
;
- shuffleDirection?: 'left' | 'right';
+ shuffleDirection?: 'left' | 'right' | 'up' | 'down';
duration?: number;
maxDelay?: number;
ease?: string | ((t: number) => number);
@@ -152,54 +152,87 @@ const build = () => {
if (!parent) return;
const w = ch.getBoundingClientRect().width;
+ const h = ch.getBoundingClientRect().height;
if (!w) return;
const wrap = document.createElement('span');
- wrap.className = 'inline-block overflow-hidden align-baseline text-left';
- Object.assign(wrap.style, { width: w + 'px' });
+ wrap.className = 'inline-block overflow-hidden text-left';
+ Object.assign(wrap.style, {
+ width: w + 'px',
+ height: props.shuffleDirection === 'up' || props.shuffleDirection === 'down' ? h + 'px' : 'auto',
+ verticalAlign: 'bottom'
+ });
const inner = document.createElement('span');
- inner.className = 'inline-block whitespace-nowrap will-change-transform origin-left transform-gpu';
+ inner.className =
+ 'inline-block will-change-transform origin-left transform-gpu ' +
+ (props.shuffleDirection === 'up' || props.shuffleDirection === 'down'
+ ? 'whitespace-normal'
+ : 'whitespace-nowrap');
parent.insertBefore(wrap, ch);
wrap.appendChild(inner);
const firstOrig = ch.cloneNode(true) as HTMLElement;
- firstOrig.className = 'inline-block text-left';
+ firstOrig.className =
+ 'text-left ' + (props.shuffleDirection === 'up' || props.shuffleDirection === 'down' ? 'block' : 'inline-block');
Object.assign(firstOrig.style, { width: w + 'px', fontFamily: computedFont });
ch.setAttribute('data-orig', '1');
- ch.className = 'inline-block text-left';
+ ch.className =
+ 'text-left ' + (props.shuffleDirection === 'up' || props.shuffleDirection === 'down' ? 'block' : 'inline-block');
Object.assign(ch.style, { width: w + 'px', fontFamily: computedFont });
inner.appendChild(firstOrig);
for (let k = 0; k < rolls; k++) {
const c = ch.cloneNode(true) as HTMLElement;
if (props.scrambleCharset) c.textContent = rand(props.scrambleCharset);
- c.className = 'inline-block text-left';
+ c.className =
+ 'text-left ' +
+ (props.shuffleDirection === 'up' || props.shuffleDirection === 'down' ? 'block' : 'inline-block');
Object.assign(c.style, { width: w + 'px', fontFamily: computedFont });
inner.appendChild(c);
}
inner.appendChild(ch);
const steps = rolls + 1;
- let startX = 0;
- let finalX = -steps * w;
- if (props.shuffleDirection === 'right') {
+ if (props.shuffleDirection === 'right' || props.shuffleDirection === 'down') {
const firstCopy = inner.firstElementChild as HTMLElement | null;
const real = inner.lastElementChild as HTMLElement | null;
if (real) inner.insertBefore(real, inner.firstChild);
if (firstCopy) inner.appendChild(firstCopy);
- startX = -steps * w;
- finalX = 0;
}
- gsap.set(inner, { x: startX, force3D: true });
+ let startX = 0;
+ let finalX = 0;
+ let startY = 0;
+ let finalY = 0;
+
+ if (props.shuffleDirection === 'right') {
+ startX = -steps * w;
+ finalX = 0;
+ } else if (props.shuffleDirection === 'left') {
+ startX = 0;
+ finalX = -steps * w;
+ } else if (props.shuffleDirection === 'down') {
+ startY = -steps * h;
+ finalY = 0;
+ } else if (props.shuffleDirection === 'up') {
+ startY = 0;
+ finalY = -steps * h;
+ }
+
+ if (props.shuffleDirection === 'left' || props.shuffleDirection === 'right') {
+ gsap.set(inner, { x: startX, y: 0, force3D: true });
+ inner.setAttribute('data-start-x', String(startX));
+ inner.setAttribute('data-final-x', String(finalX));
+ } else {
+ gsap.set(inner, { x: 0, y: startY, force3D: true });
+ inner.setAttribute('data-start-y', String(startY));
+ inner.setAttribute('data-final-y', String(finalY));
+ }
+
if (props.colorFrom) (inner.style as any).color = props.colorFrom;
-
- inner.setAttribute('data-final-x', String(finalX));
- inner.setAttribute('data-start-x', String(startX));
-
wrappersRef.value.push(wrap);
});
};
@@ -248,6 +281,7 @@ const play = () => {
if (!strips.length) return;
playingRef.value = true;
+ const isVertical = props.shuffleDirection === 'up' || props.shuffleDirection === 'down';
const tl = gsap.timeline({
smoothChildTiming: true,
@@ -255,7 +289,11 @@ const play = () => {
repeatDelay: props.loop ? props.loopDelay : 0,
onRepeat: () => {
if (props.scrambleCharset) randomizeScrambles();
- gsap.set(strips, { x: (i, t: HTMLElement) => parseFloat(t.getAttribute('data-start-x') || '0') });
+ if (isVertical) {
+ gsap.set(strips, { y: (i, t: HTMLElement) => parseFloat(t.getAttribute('data-start-y') || '0') });
+ } else {
+ gsap.set(strips, { x: (i, t: HTMLElement) => parseFloat(t.getAttribute('data-start-x') || '0') });
+ }
emit('shuffle-complete');
props.onShuffleComplete?.();
},
@@ -272,17 +310,19 @@ const play = () => {
});
const addTween = (targets: HTMLElement[], at: number) => {
- tl.to(
- targets,
- {
- x: (i, t: HTMLElement) => parseFloat(t.getAttribute('data-final-x') || '0'),
- duration: props.duration,
- ease: props.ease,
- force3D: true,
- stagger: props.animationMode === 'evenodd' ? props.stagger : 0
- },
- at
- );
+ const vars: any = {
+ duration: props.duration,
+ ease: props.ease,
+ force3D: true,
+ stagger: props.animationMode === 'evenodd' ? props.stagger : 0
+ };
+ if (isVertical) {
+ vars.y = (i: number, t: HTMLElement) => parseFloat(t.getAttribute('data-final-y') || '0');
+ } else {
+ vars.x = (i: number, t: HTMLElement) => parseFloat(t.getAttribute('data-final-x') || '0');
+ }
+
+ tl.to(targets, vars, at);
if (props.colorFrom && props.colorTo)
tl.to(targets, { color: props.colorTo, duration: props.duration, ease: props.ease }, at);
};
@@ -297,16 +337,17 @@ const play = () => {
} else {
strips.forEach(strip => {
const d = Math.random() * props.maxDelay;
- tl.to(
- strip,
- {
- x: parseFloat(strip.getAttribute('data-final-x') || '0'),
- duration: props.duration,
- ease: props.ease,
- force3D: true
- },
- d
- );
+ const vars: any = {
+ duration: props.duration,
+ ease: props.ease,
+ force3D: true
+ };
+ if (isVertical) {
+ vars.y = parseFloat(strip.getAttribute('data-final-y') || '0');
+ } else {
+ vars.x = parseFloat(strip.getAttribute('data-final-x') || '0');
+ }
+ tl.to(strip, vars, d);
if (props.colorFrom && props.colorTo)
tl.fromTo(
strip,
diff --git a/src/demo/Animations/CountUpDemo.vue b/src/demo/TextAnimations/CountUpDemo.vue
similarity index 97%
rename from src/demo/Animations/CountUpDemo.vue
rename to src/demo/TextAnimations/CountUpDemo.vue
index 98c5492..a097492 100644
--- a/src/demo/Animations/CountUpDemo.vue
+++ b/src/demo/TextAnimations/CountUpDemo.vue
@@ -71,8 +71,8 @@ import PropTable from '../../components/common/PropTable.vue';
import CliInstallation from '../../components/code/CliInstallation.vue';
import CodeExample from '../../components/code/CodeExample.vue';
import RefreshButton from '../../components/common/RefreshButton.vue';
-import CountUp from '../../content/Animations/CountUp/CountUp.vue';
-import { countup } from '@/constants/code/Animations/countUpCode';
+import CountUp from '../../content/TextAnimations/CountUp/CountUp.vue';
+import { countup } from '@/constants/code/TextAnimations/countUpCode';
import { useForceRerender } from '@/composables/useForceRerender';
import Customize from '../../components/common/Customize.vue';
import PreviewSlider from '../../components/common/PreviewSlider.vue';
diff --git a/src/demo/TextAnimations/ShuffleDemo.vue b/src/demo/TextAnimations/ShuffleDemo.vue
index ef099dc..224d257 100644
--- a/src/demo/TextAnimations/ShuffleDemo.vue
+++ b/src/demo/TextAnimations/ShuffleDemo.vue
@@ -80,7 +80,7 @@ const { rerenderKey: key, forceRerender } = useForceRerender();
const duration = ref(0.35);
const shuffleTimes = ref(1);
const stagger = ref(0.03);
-const shuffleDirection = ref<'left' | 'right'>('right');
+const shuffleDirection = ref<'left' | 'right' | 'up' | 'down'>('right');
const ease = ref('power3.out');
const loop = ref(false);
const loopDelay = ref(0);
@@ -88,7 +88,9 @@ const triggerOnHover = ref(true);
const directionOptions = [
{ label: 'Right', value: 'right' },
- { label: 'Left', value: 'left' }
+ { label: 'Left', value: 'left' },
+ { label: 'Up', value: 'up' },
+ { label: 'Down', value: 'down' }
];
const easeOptions = [
@@ -104,7 +106,7 @@ const propData = [
{ name: 'style', type: 'object', default: '{}', description: 'Inline styles applied to the wrapper element.' },
{
name: 'shuffleDirection',
- type: '"left" | "right"',
+ type: '"left" | "right" | "up" | "down"',
default: '"right"',
description: 'Direction the per-letter strip slides to reveal the final character.'
},
diff --git a/src/demo/TextAnimations/TextTypeDemo.vue b/src/demo/TextAnimations/TextTypeDemo.vue
index 0d6bc0a..cf4a9c3 100644
--- a/src/demo/TextAnimations/TextTypeDemo.vue
+++ b/src/demo/TextAnimations/TextTypeDemo.vue
@@ -17,7 +17,7 @@
-
+