Skip to content

Ripple Directive

The v-ripple directive is used to add ripple effect to any element.

Usage

vue
<template>
<!-- by default the speed will be slow and color will be #f2f2f29e -->
  <div v-ripple>Click me</div>
  <!-- handle spread speed -->
  <div v-ripple.fast">Fast Spread</div>
  <div v-ripple.slow">Slow Spread</div>
  <!-- custom ripple color(any color value eg,red,#f6f6f7) -->
<div v-ripple="'red'">Red Ripple</div>
</template>
View Code
vue
<script setup></script>
<template>
  <div class="flex space-x-2">
    <button
      v-ripple.fast
      class="bg-indigo-600 px-4 py-2 rounded-lg text-white hover:bg-indigo-700"
    >
      Fast Ripple
    </button>
    <button
      v-ripple.slow
      class="bg-indigo-600 px-4 py-2 rounded-lg text-white hover:bg-indigo-700"
    >
      Slow Ripple
    </button>
  </div>
</template>
<style scoped></style>

Arguments

The v-ripple directive does not accept any arguments.

Modifiers

The v-ripple directive accepts the following modifiers:

ModifierDescription
fastThe ripple will spread fast
slowThe ripple will spread slow

Source Code

js
// orignal code is from "https://codesandbox.io/s/v-ripple-vue-3-joji0?file=/src/directives/vRipple.js"

// I have modified it to make customisable with ability to change color, spread speed and make the click event more responsive

const handleRipple = (element, binding, ev) => {
  const rippleElement = document.createElement("span");
  let currentDiameter = 1;
  let currentOpacity = 0.65;
  applyRippleStyle();

  function applyRippleStyle() {
    const elementCoordinates = element.getBoundingClientRect();
    const offsetY = ev.clientY - elementCoordinates.top + window.scrollY; // Consider scrolling
    const offsetX = ev.clientX - elementCoordinates.left + window.scrollX; // Consider scrolling

    rippleElement.style.position = "absolute";
    rippleElement.style.height = "5px";
    rippleElement.style.width = "5px";
    rippleElement.style.borderRadius = "50%"; // Use "50%" for a perfect circle
    rippleElement.style.backgroundColor = binding.value || "#f2f2f29e";
    rippleElement.style.left = `${offsetX}px`;
    rippleElement.style.top = `${offsetY}px`;
    element.appendChild(rippleElement);
  }

  function animateRippleSpread() {
    const maximalDiameter = +binding.value || 50;
    const speedModifier = binding.modifiers && binding.modifiers.fast ? 2 : 1;

    if (currentDiameter <= maximalDiameter) {
      currentDiameter += speedModifier;
      currentOpacity -= 0.65 / (maximalDiameter * speedModifier);
      rippleElement.style.transform = `scale(${currentDiameter})`;
      rippleElement.style.opacity = `${currentOpacity}`;
    } else {
      rippleElement.remove();
      clearInterval(animationHandler);
    }
  }

  let animationHandler = setInterval(animateRippleSpread, 15);
};

export const vRipple = {
  mounted: (el, binding) => {
    el.style.position = "relative";
    el.style.overflow = "hidden";
    el.addEventListener("click", (ev) => handleRipple(el, binding, ev));
  },
};