Text Bloom Effect

This effect creates a bloom animation on text, simulating a "thinking" or "loading" state. The text appears to bloom and fade in, giving a dynamic and engaging visual effect.

TextBloom.vue
<script setup lang="ts">
const props = withDefaults(defineProps<{
  label?: string
  textColor?: string
  bloomColor?: string
  bloomIntensity?: number
  class?: string
}>(), {
  label: 'Generating summary...',
  textColor: 'text-(--ui-text-muted)',
  bloomColor: '#ffffff',
  bloomIntensity: 1.18,
  class: 'font-semibold',
})

const textBloomRef = ref<HTMLElement | null>(null)
const contentHolderRef = ref<HTMLElement | null>(null)
const animatedTextRef = ref<HTMLElement | null>(null)

const rootStyles = computed(() => ({
  '--text-color': props.textColor,
  '--bloom-color': props.bloomColor,
  '--bloom-intensity': props.bloomIntensity
}))

const updateAnimation = (): void => {
  const contentHolder = contentHolderRef.value
  const animatedText = animatedTextRef.value

  if (contentHolder && animatedText) {
    const text = contentHolder.textContent?.trim() || props.label || ''

    animatedText.innerHTML = ''

    for (let i = 0; i < text.length; i++) {
      const span = document.createElement('span')
      span.className = 'text-bloom-character'

      if (text[i] === ' ') {
        span.innerHTML = '&nbsp;'
      } else {
        span.textContent = text[i] || ''
      }

      span.style.animationDelay = `${i * 0.05}s`
      animatedText.appendChild(span)
    }
  }
}

watch(() => props.label, () => {
  nextTick(updateAnimation)
})

onMounted(updateAnimation)
onUpdated(updateAnimation)
</script>

<template>
  <div ref="textBloomRef" :style="rootStyles" class="text-bloom-container" :class="[props.class, props.textColor]">
    <div ref="contentHolderRef" class="hidden">
      <slot>{{ label }}</slot>
    </div>
    <div ref="animatedTextRef" />
  </div>
</template>

<style>
.text-bloom-container {
  perspective: 80px;
  transform-style: preserve-3d;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizelegibility;
}

.text-bloom-character {
  position: relative;
  transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
  display: inline-block;
  animation: bloom 2.4s cubic-bezier(0.25, 0.1, 0.25, 1) infinite;
  letter-spacing: 0.01em;
  transform-origin: 100% 50%;
  transform-style: preserve-3d;
  will-change: transform, color;
}

@keyframes bloom {
  0% {
    transform: translate3D(0,0,0) scale(1) rotateY(0);
    color: var(--text-color, #46afc8);
    text-shadow: 0 0 0 rgba(0,0,0,0);
  }
  12% {
    transform: translate3D(2px,-1px,2px) scale(var(--bloom-intensity, 1.18)) rotateY(6deg);
    color: var(--bloom-color, #ffffff);
  }
  15% {
    text-shadow: 0 0 1px var(--bloom-color, #ffffff);
  }
  24% {
    transform: translate3D(0,0,0) scale(1) rotateY(0);
    color: var(--text-color, #46afc8);
    opacity: 1;
  }
  36% {
    transform: translate3D(0,0,0) scale(1);
  }
  100% {
    transform: scale(1);
    opacity: 0.8;
  }
}
</style>

Props

The TextBloom component accepts the following props:

label
string

Default to Generating summary... - The default text to display when no content is provided in the slot. This text will be animated with the bloom effect.

textColor
string

Default to text-(--ui-text-muted) - The color of the text in its normal state. You can use any valid CSS color value or Tailwind CSS class.

bloomColor
string

Default to #ffffff - The color that the text will bloom to during the animation. This should be a valid hex color code.

bloomIntensity
number

Default to 1.18 - Controls the intensity of the bloom effect. Higher values create a more pronounced bloom effect.

class
string

Default to font-semibold - Additional CSS classes to apply to the component. This allows for customizing the appearance beyond the built-in props.

Usage

You can combine multiple props to customize the appearance of the TextBloom component:

<TextBloom bloomColor="#78d8f3" textColor="text-[#46afc8]" class="text-2xl font-bold">
  I am thinking...
</TextBloom>
This website is fully open-source, you can find the source code on GitHub