mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-08 16:09:31 -06:00
Merge pull request #13 from sahildavid-dev/feat/sd_text-animations_glitch-text
feat(text-animations): add GlitchText component
This commit is contained in:
189
src/content/TextAnimations/GlitchText/GlitchText.vue
Normal file
189
src/content/TextAnimations/GlitchText/GlitchText.vue
Normal 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>
|
||||
Reference in New Issue
Block a user