2 changed files with 270 additions and 53 deletions
@ -0,0 +1,203 @@ |
|||||
|
<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 = [ |
||||
|
'text-white', |
||||
|
'font-black', |
||||
|
'whitespace-nowrap', |
||||
|
'relative', |
||||
|
'mx-auto', |
||||
|
'select-none', |
||||
|
'cursor-pointer', |
||||
|
'text-[clamp(2rem,10vw,8rem)]', |
||||
|
|
||||
|
'before:content-[attr(data-text)]', |
||||
|
'before:absolute', |
||||
|
'before:top-0', |
||||
|
'before:text-white', |
||||
|
'before:bg-[#0b0b0b]', |
||||
|
'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-[#0b0b0b]', |
||||
|
'after:overflow-hidden', |
||||
|
'after:[clip-path:inset(0_0_0_0)]' |
||||
|
]; |
||||
|
|
||||
|
const normalGlitchClasses = [ |
||||
|
'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: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 = [ |
||||
|
'before:content-[""]', |
||||
|
'before:opacity-0', |
||||
|
'before:[animation:none]', |
||||
|
'after:content-[""]', |
||||
|
'after:opacity-0', |
||||
|
'after:[animation:none]', |
||||
|
|
||||
|
'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> |
||||
Loading…
Reference in new issue