v-ripple

HTML

<div id="app">
  <div v-ripple class="target">  
  </div>
  <div v-ripple.pink class="target">  
  </div>
  <div v-ripple.small.slow class="target">  
  </div>
  <div v-ripple.slow.white class="target">  
  </div>
</div>

CSS

.target {
  margin-top: 20px;
  margin-left: 100px;
  height: 100px;
  width: 100px;
  position: relative;  
  background-color: #777;    
}
.has-ripple {
  overflow: hidden;
  user-select: none;
}
.point {
  position: absolute;
  background-color: #000;
  transition: all 0.5s;
  width: 2px;
  height: 2px;
  border-radius: 999px;  
  overflow: hidden;
}
.ripple-effect {  
  animation: ripple-animation 0.4s; 
}
.ripple-effect.slow {  
  animation: ripple-animation 0.9s; 
}
.ripple-effect-small {  
  animation: ripple-animation-small 0.2s; 
}
.ripple-effect-small.slow {  
  animation: ripple-animation-small 0.7s; 
}
@keyframes ripple-animation {
    from {
      transform: scale(1);
      opacity: 0.4;
    }
    to {
      transform: scale(100);
      opacity: 0;      
    }
}
@keyframes ripple-animation-small {
    from {
      transform: scale(1);
      opacity: 0.4;
    }
    to {
      transform: scale(30);
      opacity: 0;      
    }
}

Babel + JSX

Vue.directive('ripple', {
	bind () {
    this.handler = e => {
			this.el.classList.add('has-ripple')
  		const width = parseInt(window.getComputedStyle(this.el,null).getPropertyValue('width').slice(0, -2))
      const height = parseInt(window.getComputedStyle(this.el,null).getPropertyValue('height').slice(0, -2))
      const rect = this.el.getBoundingClientRect()
      const x = e.pageX - (rect.left + document.body.scrollLeft)
      const y = e.pageY - (rect.top + document.body.scrollTop)
      let point = document.createElement('span')
      point.classList.add('point')
      if (this.modifiers.white) {
      	point.style['background-color'] = '#fff'
      } else {
      	point.style['background-color'] = this.vm.$options.ripple.color
      }
      point.style.top = `${y}px`
      point.style.left = `${x}px`
      this.el.appendChild(point)

      // 2. expand effect on point
      if (this.modifiers.small) {
      	point.classList.add('ripple-effect-small')
      } else {
      	point.classList.add('ripple-effect')
      }
      
      if (this.modifiers.slow) {
      	point.classList.add('slow')
      }

      // 3. remove ugly point
      point.addEventListener('animationend', function (e) {
        point.parentNode.removeChild(point)
      })
    }
  	this.el.addEventListener('click', this.handler)
  },
  update (newValue, oldValue) {
  	
  },
  unbind () {
  	this.el.removeEventListener('click', this.handler)
  }
})

new Vue({
	el: '#app',
  ripple: {
  	color: '#f18c16'
  }
})