#app
  ti-ripple-button(text="I'm a ripple button")
View Compiled
@import url("https://fonts.googleapis.com/css?family=Inconsolata:400,700");

$black: #000;
$white: #fff;
$shocking-pink: #ff1ead;

html, body {
  width: 100%;
  height: 100%;
}

body {
  font-family: Inconsolata, monospace;
  font-size: 24px;
  background-color: $black;
  display: grid;
  place-items: center;
}

.ti-btn {
  color: $white;
  font: inherit;
  background-color: $black;
  border: solid 4px $white;
  padding: 20px 30px;
  outline: 0;
  overflow: hidden;
  display: inline-block;
  position: relative;
  user-select: none;
  box-shadow: 0 0 0 0 rgba($white, 0.5);
  transition: box-shadow 150ms ease-out;
  &:focus {
    box-shadow: 0 0 0 8px rgba($white, 0.5);
  }
}

.ripple {
  background-color: $shocking-pink;
  width: 1rem;
  height: 1rem;
  position: absolute;
  border-radius: 50%;
  transform: translateX(-100%) translateY(-100%);
  mix-blend-mode: screen;
  animation: ripple 1250ms ease-out forwards, fade 1500ms ease-out forwards;
}

@keyframes ripple {
  0%   { transform: translate(-100%, -100%); }
  80%  { transform: translate(-100%, -100%) scale(50); }
  100% { transform: translate(-100%, -100%) scale(50); opacity: 0; }
}

@keyframes fade {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}
View Compiled
Vue.component('ti-ripple-button', {
  props: ['text'],
  data: function() {
    return {
      ripples: []
    }
  },
  methods: {
    animateRipple: function(e) {
      let el  = this.$refs.tiBtn;
      let pos = el.getBoundingClientRect();
      
      this.ripples.push({
        x: e.clientX - pos.left,
        y: e.clientY - pos.top,
        show: true
      });
    },
    rippleEnd: function(i) {
      this.ripples[i].show = false;
    }
  },
  template: `
<button class="ti-btn" ref="tiBtn" v-on:click="animateRipple">
  {{text}}
  <transition-group>
    <span
      class="ripple"
      v-bind:ref="'ripple-' + i"
      v-bind:key="'ripple' + i"
      v-for="(val, i) in ripples"
      v-if="val.show === true"
      v-bind:style="{'top': val.y + 'px', 'left': val.x + 'px'}"
      v-on:animationend="rippleEnd(i)">
    </span>
  </transition-group>
</button>
`
});

var app = new Vue({
  el: '#app'
});
View Compiled

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js