How to replicate the Material Ripple animation in Vuejs?

Viewed 174

I am trying to replicate the ripple effect from Material Design since the current app I am working on is going to get rid of Quasar; so I'm building all the elements from the ground up.

This effect: enter image description here

I've watched a few videos doing this in just pure CSS and JS and I've tried to convert it into my project, but am getting caught up somewhere. I have the hover effect and the mouse location logging properly, but the ripple effect is just not triggering and I don't know why.

Any help would be greatly appreciated! Cheers!

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>
1 Answers

Here is a working code for a button to have a ripple effect on click. Using CSS and JS:

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>

Related