Material Design Like Button Ripple Effect in CSS

Material Design Like Button Ripple Effect in CSS
Project: Versatile CSS-Only Ripple Effect
Author: Jessie
Edit Online: View on CodePen
License: MIT

This code demonstrates how to create a material design-like button ripple effect using CSS. This effect is achieved by utilizing the background-image, background-size, and background-position properties, which are all animateable properties.

The code allows the ripple effect to be applied to buttons of different colors and also supports inputs without the need for pseudo-elements. One of the key advantages of this code is that it works without requiring JavaScript, making it lightweight and efficient.

Additionally, the code is easy to customize, enabling you to apply different ripple effects such as dark, light, red, fuzzy, fast, and slow ripples. Furthermore, the code provides a foundation for extending the implementation to include other effects.

By combining radial and linear gradients, the code creates a ripple effect that expands outward and fades back to the button’s normal color. The animation is achieved by dynamically adjusting the background sizes over a small time period.

How to Create Material Design Like Button Ripple Effect

1. First of all, load the Bootstrap CSS by adding the following CDN link into the head tag of your HTML document.

<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.css'>

2. After that, create the HTML button element as follows:

<button class="btn btn-primary btn-lg btn-block"> Your Awesome Button </button>
 <!-- Ripple Effect without JS -->   
<button class="btn btn-primary btn-lg btn-block no-click-fx"> Ripple Button </button>

3. Use the following CSS code to apply ripple effect on buttons.

.btn {
  --ripple-color: rgba(0, 0, 0, 0.3);
  --ripple-duration: 1.5s;
  --ripple-fuzz: 1px;
  border-color: transparent !important;
  /* Just the ripple goes to the edges */
}
.btn:focus:not(:disabled):not(.disabled),
.btn:active:not(:disabled):not(.disabled),
.btn.click-fx:not(:disabled):not(.disabled) {
  background-image: radial-gradient(circle closest-side at center, var(--ripple-color) 0%, var(--ripple-color) calc(100% - var(--ripple-fuzz, 0px)), transparent 100%), linear-gradient(180deg, var(--ripple-color) 10%, transparent 90%);
  background-size: 0% 0%, 0% 0%;
  background-repeat: no-repeat;
  background-origin: border-box;
  -webkit-animation: button-ripple var(--ripple-duration) ease-in;
          animation: button-ripple var(--ripple-duration) ease-in;
}
.btn.pre-click-fx {
  -webkit-animation: none !important;
          animation: none !important;
}
@-webkit-keyframes button-ripple {
  0% {
    background-size: 0% 0%, 0% 0%;
    background-position: calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px)) calc(50% - var(--click-el-h, 0px)/2 + var(--click-offset-y, 0px)), 0% 0%;
  }
  33% {
    background-size: calc(2 * var(--click-max-r, 71%)) calc(2 * var(--click-max-r, 200vw)), 0% 0%;
    background-size: calc(2 * var(--click-max-r, 71%)) 100vmax, 0% 0%;
    background-position: calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px)) calc(50% - var(--click-el-h, 0px)/2 + var(--click-offset-y, 0px)), 0% 0%;
  }
  33.1% {
    background-size: 0% 0%, 100% 1000%;
  }
  100% {
    background-size: 0% 0%, 100% 1000%;
    background-position: 0% 0%, 0% 100%;
  }
}
@keyframes button-ripple {
  0% {
    background-size: 0% 0%, 0% 0%;
    background-position: calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px)) calc(50% - var(--click-el-h, 0px)/2 + var(--click-offset-y, 0px)), 0% 0%;
  }
  33% {
    background-size: calc(2 * var(--click-max-r, 71%)) calc(2 * var(--click-max-r, 200vw)), 0% 0%;
    background-size: calc(2 * var(--click-max-r, 71%)) 100vmax, 0% 0%;
    background-position: calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px)) calc(50% - var(--click-el-h, 0px)/2 + var(--click-offset-y, 0px)), 0% 0%;
  }
  33.1% {
    background-size: 0% 0%, 100% 1000%;
  }
  100% {
    background-size: 0% 0%, 100% 1000%;
    background-position: 0% 0%, 0% 100%;
  }
}
.dark-ripple {
  --ripple-color: rgba(0, 0, 0, 0.6);
}
.light-ripple {
  --ripple-color: rgba(255, 255, 255, 0.3);
}
.red-ripple {
  --ripple-color: rgba(255, 0, 0, 0.3);
}
.fuzzy-ripple {
  --ripple-fuzz: 30px;
}
.fast-ripple {
  --ripple-duration: 1s;
}
.slow-ripple {
  --ripple-duration: 3s;
}
.form-control {
  border-radius: 0;
  border: none;
  background-color: rgba(0, 0, 0, 0.1);
  transition: background-size 0.5s, background-color 0.5s, border-color 0.25s, color 0.2s, box-shadow 0.25s;
}
.form-control,
.form-control:hover,
.form-control:focus,
.form-control:active {
  background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.35) 0%, rgba(0, 0, 0, 0.35) 100%), linear-gradient(0deg, #007bff 0%, #007bff 100%);
  background-repeat: no-repeat;
  background-position: 50% 100%, 50% 100%;
  background-size: 100% 2px, 0% 2px;
}
.form-control:focus,
.form-control:active,
.form-control.click-fx {
  background-size: 100% 2px, 100% 2px;
  -webkit-animation: input-ripple 0.5s ease-in;
          animation: input-ripple 0.5s ease-in;
}
.form-control.pre-click-fx:not(.post-click-fx) {
  -webkit-animation: none !important;
          animation: none !important;
}
@-webkit-keyframes input-ripple {
  0% {
    background-position: 50% 100%, calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px)) 100%;
    background-size: 100% 2px, 0% 2px, 0% 0%;
  }
  100% {
    background-position: 50% 100%, calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px)) 100%;
    background-size: 100% 2px, calc(2 * var(--click-max-r, 71%)) 2px;
  }
}
@keyframes input-ripple {
  0% {
    background-position: 50% 100%, calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px)) 100%;
    background-size: 100% 2px, 0% 2px, 0% 0%;
  }
  100% {
    background-position: 50% 100%, calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px)) 100%;
    background-size: 100% 2px, calc(2 * var(--click-max-r, 71%)) 2px;
  }
}
.figure {
  position: relative;
  text-align: center;
  width: 100%;
  padding: 75px 75px;
  border: 2px dashed #ccc;
  margin-bottom: 24px;
  overflow: hidden;
}
.figure caption {
  position: absolute;
  top: 12px;
  left: 12px;
  right: 12px;
  color: inherit;
}
.figure .btn {
  position: relative;
  z-index: 0;
  background-image: none !important;
}
.figure .btn .mockup {
  content: '';
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  border: 2px dashed #111;
  transform: translate(-50%, -50%);
  background-repeat: no-repeat;
  z-index: -1;
}
.figure.figure-hover-animated:after,
.figure.figure-click-animated:after {
  position: absolute;
  bottom: 0px;
  left: 0px;
  width: 100%;
  background: rgba(255, 255, 255, 0.5);
  text-align: center;
  color: inherit;
  transition: opacity 0.25s;
}
.figure.figure-hover-animated:hover:after,
.figure.figure-click-animated:hover:after,
.figure-set:hover .figure.figure-hover-animated:after,
.figure-set:hover .figure.figure-click-animated:after {
  opacity: 0;
}
.figure.figure-hover-animated:after {
  content: 'Hover for Animation';
}
.figure.figure-click-animated:after {
  content: 'Click Button for Animation';
}
.figure.figure-overflow-hidden .btn {
  overflow: hidden !important;
}
.figure.figure-overflow-hidden .btn .mockup {
  border-width: 0px !important;
}
.figure.figure-ripple .btn .mockup {
  width: 0px;
  height: 0px;
  border-color: transparent;
  background-image: radial-gradient(circle closest-side at center, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.3) 99%, transparent 100%);
  transition: width 1s ease-in, height 1s ease-in, border-color 0.25s;
}
.figure.figure-ripple.figure-hover-animated:hover .btn .mockup,
.figure.figure-ripple.figure-click-animated .btn:focus,
.figure.figure-ripple .mockup,
.figure.figure-ripple.figure-click-animated .btn:active .mockup {
  width: 141%;
  height: 240px;
  border-color: #111;
}
.figure.figure-linear-fade .btn .mockup {
  width: calc(100% + 4px);
  height: calc(100% * var(--bg-height-multiplier));
  top: 0;
  transform: translate(-50%, 0%) translateY(-2px);
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) calc(100% / var(--bg-height-multiplier)), transparent calc(100% - 100% / var(--bg-height-multiplier)));
  transition: transform 1s ease-in;
}
.figure-set:hover .figure.figure-linear-fade.figure-hover-animated .btn .mockup,
.figure.figure-linear-fade.figure-hover-animated:hover .btn .mockup,
.figure.figure-linear-fade.figure-click-animated .btn:focus .mockup,
.figure.figure-linear-fade.figure-click-animated .btn:active .mockup {
  transform: translate(-50%, calc(-1 * (100% - 100% / var(--bg-height-multiplier)))) translateY(2px);
}
.figure.figure-combined .btn .mockup {
  width: 0%;
  height: 0%;
  background-image: radial-gradient(circle closest-side at center, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.3) 99%, transparent 100%), linear-gradient(180deg, rgba(0, 0, 0, 0.3) 20%, transparent 80%);
  opacity: 0;
}
.figure-set:hover .figure.figure-combined.figure-hover-animated .btn,
.figure.figure-combined.figure-hover-animated:hover .btn,
.figure.figure-combined.figure-click-animated .btn:focus,
.figure.figure-combined.figure-click-animated .btn:active,
.figure.figure-combined .btn.click-fx {
  -webkit-animation: empty-animation 5s;
          animation: empty-animation 5s;
}
.figure-set:hover .figure.figure-combined.figure-hover-animated .btn.pre-click-fx,
.figure.figure-combined.figure-hover-animated:hover .btn.pre-click-fx,
.figure.figure-combined.figure-click-animated .btn:focus.pre-click-fx,
.figure.figure-combined.figure-click-animated .btn:active.pre-click-fx,
.figure.figure-combined .btn.click-fx.pre-click-fx {
  -webkit-animation: none !important;
          animation: none !important;
}
.figure-set:hover .figure.figure-combined.figure-hover-animated .btn.pre-click-fx .mockup,
.figure.figure-combined.figure-hover-animated:hover .btn.pre-click-fx .mockup,
.figure.figure-combined.figure-click-animated .btn:focus.pre-click-fx .mockup,
.figure.figure-combined.figure-click-animated .btn:active.pre-click-fx .mockup,
.figure.figure-combined .btn.click-fx.pre-click-fx .mockup {
  -webkit-animation: none !important;
          animation: none !important;
}
.figure-set:hover .figure.figure-combined.figure-hover-animated .btn .mockup,
.figure.figure-combined.figure-hover-animated:hover .btn .mockup,
.figure.figure-combined.figure-click-animated .btn:focus .mockup,
.figure.figure-combined.figure-click-animated .btn:active .mockup,
.figure.figure-combined .btn.click-fx .mockup {
  -webkit-animation: figure-combined 5s ease-in forwards;
          animation: figure-combined 5s ease-in forwards;
  background-size: 0px 0px;
}
@-webkit-keyframes figure-combined {
  0% {
    top: calc(50% - var(--click-el-h, 0px)/2 + var(--click-offset-y, 0px));
    left: calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px));
    width: 0%;
    height: 0%;
    background-size: 100% 100%, 0% 0%;
    opacity: 1;
    transform: translate(0px, 0px);
  }
  20% {
    width: calc(2 * var(--click-max-r, 71%));
    height: calc(2 * var(--click-max-r, 95px));
    border-color: #111;
    transform: translate(calc(-1 * var(--click-max-r, 51px)), calc(-1 * var(--click-max-r, 95px)));
  }
  30% {
    border-color: transparent;
  }
  40% {
    top: calc(50% - var(--click-el-h, 0px)/2 + var(--click-offset-y, 0px));
    left: calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px));
    width: calc(2 * var(--click-max-r, 71%));
    height: calc(2 * var(--click-max-r, 95px));
    background-size: 100% 100%, 0% 0%;
    border-color: transparent;
    transform: translate(calc(-1 * var(--click-max-r, 51px)), calc(-1 * var(--click-max-r, 95px)));
  }
  40.001% {
    top: 0%;
    left: 50%;
    width: calc(100% + 4px);
    height: 500%;
    background-size: 0% 0%, 100% 100%;
    transform: translate(-38px, 0px) translateY(-2px);
  }
  45% {
    border-color: transparent;
  }
  55% {
    border-color: #111;
  }
  60% {
    transform: translate(-38px, 0px) translateY(-2px);
  }
  80% {
    transform: translate(-38px, -144px) translateY(2px);
    opacity: 1;
  }
  100% {
    top: 0%;
    width: calc(100% + 4px);
    height: 500%;
    background-size: 0% 0%, 100% 100%;
    transform: translate(-38px, -144px) translateY(2px);
    opacity: 0;
  }
}
@keyframes figure-combined {
  0% {
    top: calc(50% - var(--click-el-h, 0px)/2 + var(--click-offset-y, 0px));
    left: calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px));
    width: 0%;
    height: 0%;
    background-size: 100% 100%, 0% 0%;
    opacity: 1;
    transform: translate(0px, 0px);
  }
  20% {
    width: calc(2 * var(--click-max-r, 71%));
    height: calc(2 * var(--click-max-r, 95px));
    border-color: #111;
    transform: translate(calc(-1 * var(--click-max-r, 51px)), calc(-1 * var(--click-max-r, 95px)));
  }
  30% {
    border-color: transparent;
  }
  40% {
    top: calc(50% - var(--click-el-h, 0px)/2 + var(--click-offset-y, 0px));
    left: calc(50% - var(--click-el-w, 0px)/2 + var(--click-offset-x, 0px));
    width: calc(2 * var(--click-max-r, 71%));
    height: calc(2 * var(--click-max-r, 95px));
    background-size: 100% 100%, 0% 0%;
    border-color: transparent;
    transform: translate(calc(-1 * var(--click-max-r, 51px)), calc(-1 * var(--click-max-r, 95px)));
  }
  40.001% {
    top: 0%;
    left: 50%;
    width: calc(100% + 4px);
    height: 500%;
    background-size: 0% 0%, 100% 100%;
    transform: translate(-38px, 0px) translateY(-2px);
  }
  45% {
    border-color: transparent;
  }
  55% {
    border-color: #111;
  }
  60% {
    transform: translate(-38px, 0px) translateY(-2px);
  }
  80% {
    transform: translate(-38px, -144px) translateY(2px);
    opacity: 1;
  }
  100% {
    top: 0%;
    width: calc(100% + 4px);
    height: 500%;
    background-size: 0% 0%, 100% 100%;
    transform: translate(-38px, -144px) translateY(2px);
    opacity: 0;
  }
}

4. You can also use the following JavaScript code to create ripple effects dynamically.

// If you want to use this in your projects, feel free 
// to copy and paste it. It is dependency free and 
// should be cross-browser compatible. ^^

;(function() {
  // BEGIN POLYFILLS
  
  // .matches() Polyfill

  if (!Element.prototype.matches) {
    Element.prototype.matches = Element.prototype.msMatchesSelector || 
      Element.prototype.webkitMatchesSelector;
  }
  
  // .closest Polyfill

  if (!Element.prototype.closest) {
    Element.prototype.closest = function(s) {
      var el = this;
      if (!document.documentElement.contains(el)) return null;
      do {
        if (el.matches(s)) return el;
        el = el.parentElement || el.parentNode;
      } while (el !== null && el.nodeType === 1); 
      return null;
    };
  }
  
  // END POLYFILLS

  // BEGIN MICRO EVENT LIBRARY

  var eventHandlers = {};

  window.clickFxHandlers = eventHandlers;

  function decorateHandler(fn, selector) {
    return function(e) {
      e = e || window.event;

      if (selector && !e.target.matches(selector)) {
        return;
      }

      fn(e);
    };
  }

  function bind(el, eventsOrSelector, eventsOrFn, fnOrUndefined) {
    var selector = false;
    var events = eventsOrSelector;
    var fn = eventsOrFn;

    if (arguments.length === 4) {
      selector = eventsOrSelector;
      events = eventsOrFn;
      fn = fnOrUndefined;
    }

    var eventList = events.split(' ');
    var handler = decorateHandler(fn, selector);

    for (var i in eventList) {
      var event = eventList[i];;

      eventHandlers[event] = eventHandlers[event] || [];

      eventHandlers[event].push({
        'original': fn,
        'decorated': handler,
        'el': el,
        'selector': selector 
      });

      el.addEventListener(event, handler);
    }
  }

  function unbind(el, eventsOrSelector, eventsOrFn, fnOrUndefined) {
    var selector = false;
    var events = eventsOrSelector;
    var fn = eventsOrFn;

    if (arguments.length === 4) {
      selector = eventsOrSelector;
      events = eventsOrFn;
      fn = fnOrUndefined;
    }

    var eventList = events.split(' ');

    for (var i in eventList) {
      var event = eventList[i];

      if ('undefined' !== typeof eventHandlers[event]) {
        var handlers = eventHandlers[event];
        var hIndex = handlers.findIndex(function(handler) {
          return handler.original === fn && 
            handler.selector === selector && 
            handler.el === el;
        });

        if (-1 !== hIndex) {
          el.removeEventListener(event, handlers[hIndex].decorated);

          handlers.splice(hIndex, 1);
        }
      }
    }
  }

  function bindOnce(el, eventsOrSelector, eventsOrFn, fnOrUndefined) {
    var selector = false;
    var events = eventsOrSelector;
    var fn = eventsOrFn;

    if (arguments.length === 4) {
      selector = eventsOrSelector;
      events = eventsOrFn;
      fn = fnOrUndefined;
    }

    bind(el, selector, events, fn);
    bind(el, selector, events, function oneFn() {
      unbind(el, selector, events, fn);
      unbind(el, selector, events, oneFn); 
    });
  }
  
  // END MICRO EVENT LIBRARY
  
  // BEGIN CLICK-FX

  // Detect css animations

  function hasCssAnimation(el) {

    // get a collection of all children including self
    var items = [el].concat(Array.prototype.slice.call(el.getElementsByTagName("*")));

    // go through each item in reverse (faster)
    for (var i = items.length; i--;) {

      // get the applied styles
      var style = window.getComputedStyle(items[i], null);

      // read the animation duration - defaults to 0
      var animDuration = parseFloat(style.getPropertyValue('animation-duration') || '0');

      // if we have any duration greater than 0, an animation exists
      if (animDuration > 0) {
        return true;
      }
    }

    return false;
  }

  // Calculate and apply click-fx

  function applyClickFx(el, clickCoords) {
    if ('string' === typeof el) {
      document.querySelectorAll(el).forEach(function (oneEl) {
        applyClickFx(oneEl, clickCoords);
      });
      
      return;
    }
    
    if (el.classList.contains('click-fx') || el.classList.contains('pre-click-fx') || el.classList.contains('no-click-fx')) {
      return;
    }

    var cssVars = {};

    if (clickCoords) {
      let elOffset = el.getBoundingClientRect();
      let clickOffset = {
        x: Math.round(clickCoords.x - elOffset.left),
        y: Math.round(clickCoords.y - elOffset.top)
      };

      cssVars['--click-offset-x'] = clickOffset.x + 'px';
      cssVars['--click-offset-y'] = clickOffset.y + 'px';
      cssVars['--click-max-r'] = Math.round(Math.sqrt(
        Math.pow(Math.max(clickOffset.x, el.offsetWidth - clickOffset.x), 2) + 
        Math.pow(Math.max(clickOffset.y, el.offsetHeight - clickOffset.y), 2)
      )) + 'px';
      cssVars['--click-el-w'] = el.offsetWidth + 'px';
      cssVars['--click-el-h'] = el.offsetHeight + 'px';
    }

    for (var cssVar in cssVars) {
      el.style.setProperty(cssVar, cssVars[cssVar]);
    }

    el.classList.add('pre-click-fx');

    requestAnimationFrame(function(){
      // If its ignoring reset, exit early
      if (hasCssAnimation(el)) {
        el.classList.remove('pre-click-fx');
        return;   
      }

      el.classList.add('click-fx');
      el.classList.remove('pre-click-fx');

      bindOnce(el, 'animationend webkitAnimationEnd oAnimationEnd animationcancel webkitAnimationCancel oAnimationCancel', function(e) {
        el.classList.remove('click-fx');
        for (var cssVar in cssVars) {
          el.style.removeProperty(cssVar);
        }

        if (el.matches('input:not([type=submit]):not([type=button]):not([type=reset]), textarea, select') && el === document.activeElement) {
          // This is a stateful element and so might have a
          // stateful effect
          
          el.classList.add('post-click-fx');

          // Give other plugins a chance to do their magic to
          // the element (e.g. select2 hidden input) before checking
          // if it is still focused
          
          bind(document.querySelector('body'), 'mouseup touchend keyup', function longBlurHandler(e) {
            setTimeout(function() {
              if (el !== document.activeElement) {
                // No longer focused
                el.classList.remove('post-click-fx');
                unbind(document.querySelector('body'), 'mouseup touchend keyup', longBlurHandler);
              }
            }, 0);
          });
        }
      });
    });
  }
  
  // API

  window.clickFx = function (selector) {
    var lastEvent = null;

    bind(document.querySelector('body'), selector + ',' + selector.replace(/,/g, ' *,'), 'mousedown touchstart touchend focusin', function(e) {
      var ignoreEvent = false;

      if (
        lastEvent &&
        'mousedown' === e.type && 'touchend' === lastEvent.type && 
        e.target === lastEvent.target
      ) {
        // This is a mousedown fired automatically after a touchend on same target
        ignoreEvent = true;
      } else if ('touchend' === e.type) {
        ignoreEvent = true;
      }

      lastEvent = e;

      if (ignoreEvent) {
        return;
      }

      var el = e.target;

      if (!el.matches(selector)) {
        el = el.closest(selector);
      }

      var clickCoords = false;

      if ('focusin' !== e.type) {
        // This is a click event
        
        clickCoords = {
          x: e.clientX,
          y: e.clientY
        };

        if (e.changedTouches && e.changedTouches[0]) {
          clickCoords.x = e.changedTouches[0].clientX;
          clickCoords.y = e.changedTouches[0].clientY;
        }
      }
      
      // Apply actual effect

      applyClickFx(el, clickCoords);

      // Check if this el should trigger effect on other els

      if ('undefined' !== typeof el.dataset.applyClickFx) {
        document.querySelectorAll(el.dataset.applyClickFx).forEach(function(otherEl) {
          applyClickFx(otherEl, clickCoords);
        });

        let parent = el.parent;

        while (parent) {
          if ('undefined' !== parent.dataset.applyClickFx) {
            document.querySelectorAll(parent.dataset.applyClickFx).forEach(function(otherEl) {
              applyClickFx(otherEl, clickCoords);
            });
          }

          parent = parent.parent;
        }
      }

      // Check if other els should be triggered by this el

      document.querySelectorAll('[data-subscribe-click-fx]').forEach(function(otherEl) {
        if (el.matches(otherEl.dataset.subscribeClickFx)) {
          applyClickFx(otherEl, clickCoords);
        }
      });
    });
  };
  
  // END CLICK-FX

  // BEGIN DEMO (You can get rid of this)

  bind(document, 'DOMContentLoaded', function() {
    clickFx('a, input, textarea, button, .btn');
  });

  bind(document.querySelector('body'), '.linked-ripple, .linked-ripple *', 'mousedown touchstart touchend', function(e) {
    var targetEl = e.target;
    var clientX = e.clientX;
    var clientY = e.clientY;
    var targetElRect = targetEl.getBoundingClientRect();
    var relativeOffset = {
      x: Math.round(clientX - targetElRect.left),
      y: Math.round(clientY - targetElRect.top)
    };

    document.querySelectorAll('.linked-ripple').forEach(function(linkedEl) {
      if (linkedEl === targetEl) {
        return;
      }
      
      var elRect = linkedEl.getBoundingClientRect();

      applyClickFx(linkedEl, {
        x: elRect.left + relativeOffset.x,
        y: elRect.top + relativeOffset.y
      });
    });
  });

  if ('undefined' !== typeof window._l) {
    // Catch peoples eye when they see this in the preview
    
    Array.from(document.querySelectorAll('.btn:not(.css-only)')).slice(0, 14).forEach(function(el, i) {
      var elRect = el.getBoundingClientRect();

      setTimeout(function() {
        applyClickFx(el, {
          x: elRect.left + 10,
          y: elRect.top + 10
        });
      }, i * 100); 
    });
  }

  // END DEMO
})();

That’s all! hopefully, you have successfully created Material Design like button ripple effect in CSS. If you have any questions or suggestions, feel free to comment below.

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *