Merge pull request #13 from sahildavid-dev/feat/sd_text-animations_glitch-text

feat(text-animations): add GlitchText component
This commit is contained in:
David
2025-07-12 18:45:21 +03:00
committed by GitHub
6 changed files with 339 additions and 2 deletions

View File

@@ -0,0 +1,33 @@
<template>
<div class="preview-text">
<span class="text-label">{{ title }}</span>
<input
:value="modelValue"
@input="handleChange"
class="w-[300px] px-3 py-2 bg-[#0b0b0b] border border-[#333] rounded-md text-white focus:outline-none focus:border-[#666]"
/>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(['update:modelValue']);
defineProps<{
title: string;
modelValue: string;
}>();
const handleChange = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
};
</script>
<style scoped>
.preview-text {
display: flex;
align-items: center;
gap: 1rem;
margin: 1.5rem 0;
}
</style>

View File

@@ -22,7 +22,8 @@ export const CATEGORIES = [
'True Focus', 'True Focus',
'Scroll Float', 'Scroll Float',
'Scroll Reveal', 'Scroll Reveal',
'Rotating Text' 'Rotating Text',
'Glitch Text'
] ]
}, },
{ {

View File

@@ -27,7 +27,8 @@ const textAnimations = {
'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"),
'rotating-text': ()=> import("../demo/TextAnimations/RotatingTextDemo.vue") 'rotating-text': ()=> import("../demo/TextAnimations/RotatingTextDemo.vue"),
'glitch-text': () => import("../demo/TextAnimations/GlitchTextDemo.vue"),
}; };
const components = { const components = {

View File

@@ -0,0 +1,19 @@
import code from '@/content/TextAnimations/GlitchText/GlitchText.vue?raw';
import type { CodeObject } from '../../../types/code';
export const glitchText: CodeObject = {
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/GlitchText`,
usage: `<template>
<GlitchText
children="Vue Bits"
:speed="0.5"
:enable-shadows="true"
:enable-on-hover="false"
/>
</template>
<script setup lang="ts">
import GlitchText from "./GlitchText.vue";
</script>`,
code
};

View File

@@ -0,0 +1,189 @@
<template>
<div :class="computedClasses" :style="inlineStyles" :data-text="children">
{{ children }}
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { CSSProperties } from 'vue';
interface GlitchTextProps {
children: string;
speed?: number;
enableShadows?: boolean;
enableOnHover?: boolean;
className?: string;
}
interface CustomCSSProperties extends CSSProperties {
'--after-duration': string;
'--before-duration': string;
'--after-shadow': string;
'--before-shadow': string;
}
const props = withDefaults(defineProps<GlitchTextProps>(), {
speed: 0.5,
enableShadows: true,
enableOnHover: false,
className: ''
});
const inlineStyles = computed(
(): CustomCSSProperties => ({
'--after-duration': `${props.speed * 3}s`,
'--before-duration': `${props.speed * 2}s`,
'--after-shadow': props.enableShadows ? '-5px 0 red' : 'none',
'--before-shadow': props.enableShadows ? '5px 0 cyan' : 'none'
})
);
const baseClasses = [
// Base styling
'text-white',
'font-black',
'whitespace-nowrap',
'relative',
'mx-auto',
'select-none',
'cursor-pointer',
'text-[clamp(2rem,10vw,8rem)]',
// Pseudo-elements base
'before:content-[attr(data-text)]',
'before:absolute',
'before:top-0',
'before:text-white',
'before:bg-[#060010]',
'before:overflow-hidden',
'before:[clip-path:inset(0_0_0_0)]',
'after:content-[attr(data-text)]',
'after:absolute',
'after:top-0',
'after:text-white',
'after:bg-[#060010]',
'after:overflow-hidden',
'after:[clip-path:inset(0_0_0_0)]'
];
const normalGlitchClasses = [
// After pseudo-element for normal mode
'after:left-[10px]',
'after:[text-shadow:var(--after-shadow,-10px_0_red)]',
'after:[animation:animate-glitch_var(--after-duration,3s)_infinite_linear_alternate-reverse]',
// Before pseudo-element for normal mode
'before:left-[-10px]',
'before:[text-shadow:var(--before-shadow,10px_0_cyan)]',
'before:[animation:animate-glitch_var(--before-duration,2s)_infinite_linear_alternate-reverse]'
];
const hoverOnlyClasses = [
// Hide pseudo-elements by default
'before:content-[""]',
'before:opacity-0',
'before:[animation:none]',
'after:content-[""]',
'after:opacity-0',
'after:[animation:none]',
// Show and animate on hover
'hover:before:content-[attr(data-text)]',
'hover:before:opacity-100',
'hover:before:left-[-10px]',
'hover:before:[text-shadow:var(--before-shadow,10px_0_cyan)]',
'hover:before:[animation:animate-glitch_var(--before-duration,2s)_infinite_linear_alternate-reverse]',
'hover:after:content-[attr(data-text)]',
'hover:after:opacity-100',
'hover:after:left-[10px]',
'hover:after:[text-shadow:var(--after-shadow,-10px_0_red)]',
'hover:after:[animation:animate-glitch_var(--after-duration,3s)_infinite_linear_alternate-reverse]'
];
const computedClasses = computed(() => {
const classes = [...baseClasses];
if (props.enableOnHover) {
classes.push(...hoverOnlyClasses);
} else {
classes.push(...normalGlitchClasses);
}
if (props.className) {
classes.push(props.className);
}
return classes.join(' ');
});
</script>
<style>
@keyframes animate-glitch {
0% {
clip-path: inset(20% 0 50% 0);
}
5% {
clip-path: inset(10% 0 60% 0);
}
10% {
clip-path: inset(15% 0 55% 0);
}
15% {
clip-path: inset(25% 0 35% 0);
}
20% {
clip-path: inset(30% 0 40% 0);
}
25% {
clip-path: inset(40% 0 20% 0);
}
30% {
clip-path: inset(10% 0 60% 0);
}
35% {
clip-path: inset(15% 0 55% 0);
}
40% {
clip-path: inset(25% 0 35% 0);
}
45% {
clip-path: inset(30% 0 40% 0);
}
50% {
clip-path: inset(20% 0 50% 0);
}
55% {
clip-path: inset(10% 0 60% 0);
}
60% {
clip-path: inset(15% 0 55% 0);
}
65% {
clip-path: inset(25% 0 35% 0);
}
70% {
clip-path: inset(30% 0 40% 0);
}
75% {
clip-path: inset(40% 0 20% 0);
}
80% {
clip-path: inset(20% 0 50% 0);
}
85% {
clip-path: inset(10% 0 60% 0);
}
90% {
clip-path: inset(15% 0 55% 0);
}
95% {
clip-path: inset(25% 0 35% 0);
}
100% {
clip-path: inset(30% 0 40% 0);
}
}
</style>

View File

@@ -0,0 +1,94 @@
<template>
<div class="glitchtext-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative h-[500px] overflow-hidden">
<GlitchText
:children="text"
:speed="speed"
:enable-shadows="enableShadows"
:enable-on-hover="enableOnHover"
class-name="demo-glitch-text"
/>
</div>
<Customize>
<div class="mb-4">
<PreviewText title="Text" v-model="text" />
<PreviewSlider title="Refresh Delay" v-model="speed" :min="0.1" :max="5" :step="0.1" />
<PreviewSwitch title="Glitch Colors" v-model="enableShadows" />
<PreviewSwitch title="Glitch On Hover" v-model="enableOnHover" />
</div>
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="glitchText" />
</template>
<template #cli>
<CliInstallation :command="glitchText.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import TabbedLayout from '../../components/common/TabbedLayout.vue';
import CodeExample from '../../components/code/CodeExample.vue';
import CliInstallation from '../../components/code/CliInstallation.vue';
import GlitchText from '../../content/TextAnimations/GlitchText/GlitchText.vue';
import PropTable from '../../components/common/PropTable.vue';
import PreviewText from '../../components/common/PreviewText.vue';
import PreviewSlider from '../../components/common/PreviewSlider.vue';
import PreviewSwitch from '../../components/common/PreviewSwitch.vue';
import { glitchText } from '@/constants/code/TextAnimations/glitchTextCode';
const text = ref('Vue Bits');
const speed = ref(0.5);
const enableShadows = ref(true);
const enableOnHover = ref(false);
watch(enableOnHover, newValue => {
text.value = newValue ? 'Hover Me' : 'Vue Bits';
});
const propData = [
{
name: 'children',
type: 'string',
default: '',
description: 'The text content that will display the glitch effect.'
},
{
name: 'speed',
type: 'number',
default: '0.5',
description: 'Multiplier for the animation speed. Higher values slow down the glitch effect.'
},
{
name: 'enableShadows',
type: 'boolean',
default: 'true',
description: 'Toggle the colored text shadows on the glitch pseudo-elements.'
},
{
name: 'enableOnHover',
type: 'boolean',
default: 'false',
description: 'If true, the glitch animation is only activated on hover.'
},
{
name: 'className',
type: 'string',
default: '',
description: 'Additional custom classes to apply to the component.'
}
];
</script>