Skip to content

Ripple

An animated ripple effect typically used behind elements to emphasize them.

Ripple

View source on GitHub

Installation

Copy the component files below into src/components/spark-ui/ripple/. Utility imports use @/lib/utils.

ripple.vue
vue
<script setup lang="ts">
import { cn } from "@/lib/utils";

interface RippleProps {
  mainCircleSize?: number;
  mainCircleOpacity?: number;
  numCircles?: number;
  class?: string;
}

const props = withDefaults(defineProps<RippleProps>(), {
  mainCircleSize: 210,
  mainCircleOpacity: 0.24,
  numCircles: 8,
});
</script>

<template>
  <div
    :class="
      cn(
        'absolute inset-0 bg-white/5 [mask-image:linear-gradient(to_bottom,white,transparent)]',
        props.class,
      )
    "
  >
    <div v-for="(_, i) in Array.from({ length: props.numCircles })" :key="i">
      <div
        :key="i"
        class="absolute animate-ripple rounded-full bg-foreground/25 shadow-xl border"
        :style="{
          width: `${props.mainCircleSize + i * 70}px`,
          height: `${props.mainCircleSize + i * 70}px`,
          opacity: props.mainCircleOpacity - i * 0.03,
          '--i': i,
          animationDelay: 'calc(var(--i) * 0.06s)',
          borderStyle: i === props.numCircles - 1 ? 'dashed' : 'solid',
          borderWidth: '1px',
          borderColor: `hsl(var(--foreground) / ${(5 + i * 5) / 100})`,
          top: '50%',
          left: '50%',
          transform: 'translate(-50%, -50%) scale(1)',
        }"
      />
    </div>
  </div>
</template>

Add the following code to your ./src/assets/css/tailwind.css file:

tailwind.css
css
@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
  }
}

Then, add the following colors and animations to your tailwind.config.js file:

tailwind.config.js
js
module.exports = {
  theme: {
    extend: {
      colors: {
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
      },
      animation: {
        ripple: "ripple var(--duration,2s) ease calc(var(--i, 0)*.2s) infinite",
      },
      keyframes: {
        ripple: {
          "0%, 100%": {
            transform: "translate(-50%, -50%) scale(1)",
          },
          "50%": {
            transform: "translate(-50%, -50%) scale(0.9)",
          },
        },
      },
    },
  },
};

Behavior

Each ring supplies its index as an inline --i custom property. The inline animation delay preserves the 0.06-second stagger, and borders use a valid HSL alpha value.

Props

PropTypeDefaultDescription
mainCircleSizenumber210The size of the main circle in pixels
mainCircleOpacitynumber0.24The opacity of the main circle
numCirclesnumber8The number of ripple circles to render

Released under the MIT License.