Guide to recreating the Material Ripple effect using Vuejs

I am in the process of recreating the ripple effect inspired by Material Design for my current project. I have decided to move away from Quasar and build all the elements from scratch.

Here is the effect:

I have seen some tutorials on creating this effect using pure CSS and JS, and I attempted to integrate it into my project. However, I am encountering difficulties as the ripple effect is not triggering despite having the hover effect and proper logging of mouse location.

Any assistance with resolving this issue would be highly appreciated! Thank you!

Access CodeSandbox Code

CButton.vue

<template>
  <button
    @click="onClick"
    :class="[
      'c-btn',
      `c-btn--${kind}`,
      disabled ? `_disabled` : '',
      kind === 'icon-round' ? 'shadow-5' : '',
    ]"
  >
    <transition
      name="ripple"
      @enter="rippleEnter"
      @after-enter="afterRippleEnter"
    >
      <span v-if="ripple" ref="ripple" class="ripple" />
    </transition>
    <div class="_inner">
      <div class="_text">
        <slot>{{ btnText }}</slot>
      </div>
    </div>
  </button>
</template>

<script>
export default {
  name: "CcBtn",
  components: {},
  props: {
    btnText: { type: String },
    kind: { type: String, default: "main" },
    isBusy: { type: Boolean, default: false },
    /**
     * HTML5 attribute
     * @category state
     */
    disabled: { type: Boolean, default: false },
    color: { type: String, default: "" },
  },
  data() {
    return {
      ripple: false,
      x: 0,
      y: 0,
    };
  },
  methods: {
    onClick(e) {
      this.x = e.layerX;
      this.y = e.layerY;
      this.ripple = !this.ripple;
      console.log(`x`, this.x);
      console.log(`y`, this.y);
      console.log(`ripple`, this.ripple);
    },
    rippleEnter() {
      this.$refs.ripple.style.top = `${this.y}px`;
      this.$refs.ripple.style.left = `${this.x}px`;
    },
    afterRippleEnter() {
      this.ripple = false;
    },
  },
};
</script>

<style lang="sass" scoped>
.c-btn
  color: white
  padding: 10px 16px
  border-radius: 4px
  line-height: 1em
  min-height: 2em
  font-weight: bold
  font-size: 16px
  color: White
  cursor: pointer
  border: 1px solid transparent
  transition: background-color 0.5s
  ._inner
    display: flex
    align-items: center
    justify-content: center
  &--main
    background: #9759ff
    min-width: 228px
    border-radius: 100px
    &:hover
      background-color: lighten(#9759ff, 10%)

  &--sub
    background: #f3eefe
    min-width: 228px
    border-radius: 100px
    color: black
    &:hover
      background-color: darken(#f3eefe, 5%)

.ripple
  display: block
  width: 20px
  height: 20px
  border-radius: 10px
  position: absolute
  top: 0
  left: 0
  pointer-events: none
  background-color: rgba(lighten(#9759ff, 20%), 0.8)
  opacity: 0
  transform: translate(-50%, -50%) scale(10)
  transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out
  &-enter
    opacity: 1
    transform: translate(-50%, -50%) scale(0)
</style>

App.vue

<template>
  <CButton :btnText="'Button'" kind="main" />
  <br />
  <br />
  <br />
  <CButton :btnText="'Button'" kind="sub" />
</template>

<script>
import CButton from "./components/CButton.vue";
export default {
  name: "App",
  components: {
    CButton,
  },
};
</script>

Answer №1

Below is a functional code snippet for creating a button with a ripple effect when clicked, using a combination of CSS and JavaScript:

function createRipple(event) {
  const button = event.currentTarget;

  const circle = document.createElement("span");
  const diameter = Math.max(button.clientWidth, button.clientHeight);
  const radius = diameter / 2;

  circle.style.width = circle.style.height = `${diameter}px`;
  circle.style.left = `${event.clientX - button.offsetLeft - radius}px`;
  circle.style.top = `${event.clientY - button.offsetTop - radius}px`;
  circle.classList.add("ripple");

  const ripple = button.getElementsByClassName("ripple")[0];

  if (ripple) {
    ripple.remove();
  }

  button.appendChild(circle);
}

const buttons = document.getElementsByTagName("button");
for (const button of buttons) {
  button.addEventListener("click", createRipple);
}
body {
  height: 100vh;
  margin: 0;
  display: grid;
  place-items: center;
}

@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');

button {
  position: relative;
  overflow: hidden;
  transition: background 400ms;
  color: #fff;
  background-color: #ff0000;
  padding: 1rem 2rem;
  font-family: 'Roboto', sans-serif;
  font-size: 1.5rem;
  outline: 0;
  border: 0;
  border-radius: 0.25rem;
  box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.3); /* black with 30% opacity */
  cursor: pointer;
}

span.ripple {
  position: absolute;
  border-radius: 50%;
  transform: scale(0);
  animation: ripple 600ms linear;
  background-color: rgba(255, 255, 255, 0.7);
}

@keyframes ripple {
  to {
    transform: scale(4);
    opacity: 0;
  }
}
<button>Click For Effect</button>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Having trouble establishing a connection between my Vue application and the local port

Whenever I execute npm run dev command to connect my application for viewing in the browser, it keeps throwing this error: > sh:/Users/jasmineanderson/chibi/chibi_hub/client/node_modules/.bin/webpack-dev-server: Permission denied > npm ER ...

What sets apart v-model from :model-value?

I'm curious because I have trouble grasping the distinction between v-model and :model-value. According to the information in the documentation: v-model is used on a native element: <input v-model="searchText"/> However, when used on ...

KnockoutJS - Using containerless control flow binding with predefined values

Inside a select control, I am using ko:foreach instead of the usual bindings. Everything is working perfectly, except that the initial value for "specialProperty" is set to unknown even when the select control is set to Option 1. It behaves as expected o ...

Why does the ReactJS MaterialUI Modal fail to update properly?

Recently, I encountered a curious issue with my Modal component: https://i.stack.imgur.com/dkj4Q.png When I open the dropdown and select a field, it updates the state of the Object but fails to render it in the UI. Strangely, if I perform the same action ...

Why is React JS unable to discover my exported modules?

Upon running my React app, the console displayed the following error message: Failed to compile. ./src/components/login/index.js Attempted import error: 'Login' is not exported from './login'. Here is an overview of the folder struct ...

Updating Vue component with mismatched props

I am looking to optimize the Vue component where data is received in varying structures. Take for example Appointment.vue component: <template> <div> <div v-if="config.data.user.user_id"> {{ config.data.user.user_id ...

Error: The module you are trying to import from the package is not found. Please check the package path and make sure that

I encountered an issue when trying to compile a code in Reactjs. As a beginner in Reactjs, I'm struggling with this. Module not found: Error: Package path ./cjs/react.development is not exported from package /Users/mansi/letsgrowmore/to-do-list/my-rea ...

What is the process for setting environment variables in a Svelte project?

I'm brand new to working with Svelte and I want to incorporate environment variables like base_url into different components. I've read that I can set up a store to hold these values, for example: const DataStore = writable([ { base_url: & ...

Is there a way to set up an automatic pop-up for this?

Experience this code function AutoPopup() { setTimeout(function () { document.getElementById('ac-wrapper').style.display = "block"; }, 5000); } #ac-wrapper { position: fixed; top: 0; left: 0; width: 100%; height: 100%; back ...

Embed Socket.IO into the head tag of the HTML document

After working with socket.IO and starting off with a chat example, my chat service has become quite complex. The foundation of my code is from the original tutorial where they included <script src="/socket.io/socket.io.js"></script> <scrip ...

Display all items with pagination in a Material UI Table using React

I have recently implemented pagination in a react data table to handle a large number of entries. I wanted to add an option to display all entries by selecting "all" in the rowsPerPageOptions dropdown menu. Currently, I am able to show the count of all ent ...

Tips for encapsulating a promise while maintaining the original return type

In my code, there is a function that utilizes an axios instance and is of type async function register(data: RegisterData): Promise<AxiosResponse<UserResponse, any>> export const register = (data: RegisterData) => api.post<UserResponse> ...

Difficulty accessing context.params query in Next.js Dynamic Path

I've almost completed setting up a dynamic page in Next.js using getStaticPaths(), but I'm running into an issue with the getStaticProps() function not referencing the URL query correctly to load the relevant information. Here is my code: //Get ...

Inject a dynamic URL parameter into an iframe without the need for server-side scripting

I'm really stuck and could use some assistance with the following issue, as I am unable to solve it on my own :( When a user is redirected to a form (provided via an iframe), there is a dynamic URL involved: website.com/form?id=123 The code resp ...

"Exploring the challenges of implementing a jquerytools tooltip with AJAX

Currently, I have set up an ajax call to run every 15 seconds. The issue arises when the ajax call disables the tooltip if it's open at that moment for a particular item. This results in the destruction of only the tooltip being displayed, leaving oth ...

Coldbox handler does not receive the data from AJAX call

In my current project, I encountered a strange issue while making an $.ajax call to a Coldbox handler method. When I dump the rc scope at the beginning of the handler, there's no data in it other than my usual additions on each request. Even more biz ...

After integrating Vue + Inertia into my Laravel 10 project, I encountered an issue where I am receiving an error stating 'autocomplete not a function' when trying to load a Vue

BACKGROUND: My Laravel application is well-established and developed using Laravel, Blade, Javascript, and JQuery. Loading a blade view from the Laravel section of the site does not show any errors in Chrome dev tools. It's important to note that I ut ...

In an AngularJS custom filter function, the error message "keys is not defined" is displayed

As I was reviewing examples in an Angular JS book, I came across a concept that has left me puzzled. It involves the use of custom filters with ng-repeat. Below are the code snippets: <a ng-click="selectCategory()" class="btn btn-block btn-default btn- ...

Exploring Object Arrays with Underscore.js

Here is an array of objects that I am working with: var items = [ { id: 1, name: "Item 1", categories: [ { id: 1, name: "Item 1 - Category 1" }, { ...

Implement a function to delete an item from an array within a React object by utilizing the UseState hook

I am facing a challenge in React as I attempt to remove an element from an array within an array of objects using the UseState hook. Despite my efforts, the interface does not re-render and the object remains in place. From what I understand, in order for ...