Files
vue-bits/public/r/BlurText.json
2026-02-02 11:38:18 +05:30

1 line
4.2 KiB
JSON

{"name":"BlurText","title":"BlurText","description":"Text starts blurred then crisply resolves for a soft-focus reveal effect.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <p ref=\"rootRef\" class=\"flex flex-wrap blur-text\" :class=\"className\">\n <Motion\n v-for=\"(segment, index) in elements\"\n :key=\"index\"\n tag=\"span\"\n :initial=\"fromSnapshot\"\n :animate=\"inView ? buildKeyframes(fromSnapshot, toSnapshots) : fromSnapshot\"\n :transition=\"getTransition(index)\"\n @animation-complete=\"handleAnimationComplete(index)\"\n style=\"display: inline-block; will-change: transform, filter, opacity\"\n >\n {{ segment === ' ' ? '\\u00A0' : segment }}\n <template v-if=\"animateBy === 'words' && index < elements.length - 1\">&nbsp;</template>\n </Motion>\n </p>\n</template>\n\n<script setup lang=\"ts\">\nimport { Motion, type Transition } from 'motion-v';\nimport { computed, onBeforeUnmount, onMounted, ref, useTemplateRef } from 'vue';\n\ntype BlurTextProps = {\n text?: string;\n delay?: number;\n className?: string;\n animateBy?: 'words' | 'letters';\n direction?: 'top' | 'bottom';\n threshold?: number;\n rootMargin?: string;\n animationFrom?: Record<string, string | number>;\n animationTo?: Array<Record<string, string | number>>;\n easing?: (t: number) => number;\n onAnimationComplete?: () => void;\n stepDuration?: number;\n};\n\nconst buildKeyframes = (\n from: Record<string, string | number>,\n steps: Array<Record<string, string | number>>\n): Record<string, Array<string | number>> => {\n const keys = new Set<string>([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes: Record<string, Array<string | number>> = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst props = withDefaults(defineProps<BlurTextProps>(), {\n text: '',\n delay: 200,\n className: '',\n animateBy: 'words',\n direction: 'top',\n threshold: 0.1,\n rootMargin: '0px',\n easing: (t: number) => t,\n stepDuration: 0.35\n});\n\nconst inView = ref(false);\nconst rootRef = useTemplateRef<HTMLParagraphElement>('rootRef');\nlet observer: IntersectionObserver | null = null;\n\nonMounted(() => {\n if (!rootRef.value) return;\n\n observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n inView.value = true;\n observer?.unobserve(rootRef.value as Element);\n }\n },\n {\n threshold: props.threshold,\n rootMargin: props.rootMargin\n }\n );\n\n observer.observe(rootRef.value);\n});\n\nonBeforeUnmount(() => {\n observer?.disconnect();\n});\n\nconst elements = computed(() => (props.animateBy === 'words' ? props.text.split(' ') : props.text.split('')));\n\nconst defaultFrom = computed(() =>\n props.direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 }\n);\n\nconst defaultTo = computed(() => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: props.direction === 'top' ? 5 : -5\n },\n {\n filter: 'blur(0px)',\n opacity: 1,\n y: 0\n }\n]);\n\nconst fromSnapshot = computed(() => props.animationFrom ?? defaultFrom.value);\nconst toSnapshots = computed(() => props.animationTo ?? defaultTo.value);\n\nconst stepCount = computed(() => toSnapshots.value.length + 1);\nconst totalDuration = computed(() => props.stepDuration * (stepCount.value - 1));\n\nconst times = computed(() =>\n Array.from({ length: stepCount.value }, (_, i) => (stepCount.value === 1 ? 0 : i / (stepCount.value - 1)))\n);\n\nconst getTransition = (index: number): Transition => ({\n duration: totalDuration.value,\n times: times.value,\n delay: (index * props.delay) / 1000,\n ease: props.easing\n});\n\nconst handleAnimationComplete = (index: number) => {\n if (index === elements.value.length - 1) {\n props.onAnimationComplete?.();\n }\n};\n</script>\n","path":"BlurText/BlurText.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"motion-v","version":"^1.10.2"}],"devDependencies":[],"categories":["TextAnimations"]}