Water Ripple Animation with CSS

As part of my work volunteering at 48in48, I got to play around with CSS animations recently. For the Pebble Tossers website, I wanted to build an engaging animation that both reflected the spirit of the organization and enticed users to sign up as volunteers–so I added a water-ripple effect behind the call-to-action buttons.

 

Check out the effect below. When you hover over the animation, it pauses.

 

Building the animation

I created a SASS mixin so that the animation can be attached to any element. It accepts 3 parameters:

  1. A CSS selector to which the animation is attached
  2. The background color of the animation container
  3. The size of the ripple animation

The containing element for the animation can have children elements–the animation will also be attached to the first child.

Sample markup

<div class="ripple-container">
     <a href="#" class="btn btn-default ripple-wrapper">
          <span>Hover over me!</span>
     </a>
 </div>

Including the SASS mixin

@include ripples(".ripple-wrapper", $primaryBgColor, 75);

SASS mixin

@mixin ripples($rippleContainer, $rippleContainerBgColor, $rippleSize) {
  #{$rippleContainer} {

    position: relative;
    z-index: 0;

    &:before,
    &:after,
    > *:first-child:before,
    > *:first-child:after {
      content: '';
      display: block;
      position: absolute;
      top: 25%;
      left: 50%;
      width: 2px;
      height: 2px;
      border-radius: 2px;
      animation: ripple 4s infinite;
      backface-visibility: hidden;
  }

  &:before {
      z-index: -4;
      margin-left: 5px;
      animation-delay: 0;
    }

    &:after {
      z-index: -2;
      margin-left: 8px;
      animation-delay:.5s;
    }

    > *:first-child:before {
      z-index: -3;
      margin-left: -3px;
      animation-delay:.3s;
    }

    > *:first-child:after {
      z-index: -1;
      margin-left: -8px;
      animation-delay: .8s;
    }

    &:hover:before,
    &:hover:after,
    > *:first-child:hover:before,
    > *:first-child:hover:after {
      animation-play-state: paused;
    }

    @keyframes ripple {
      0%  {
        box-shadow:0 0 0 0 transparent,
                   0 0 0 0 transparent,
                   0 0 0 0 transparent,
                   0 0 0 0 transparent;
      }
      5%  {
        box-shadow:0 0 0 0 $rippleContainerBgColor,
                   0 0 0 0 rgba(255,255,255,0.5),
                   0 0 0 0 $rippleContainerBgColor,
                   0 0 0 0 rgba(0,0,0,0.2);
      }
      75% {
        box-shadow:0 0 ($rippleSize/2)+px $rippleSize+px $rippleContainerBgColor,
                   0 0 ($rippleSize/($rippleSize/10))+px ($rippleSize+($rippleSize/20))+px transparent,
                   0 0 ($rippleSize/3)+px ($rippleSize + ($rippleSize/20))+px $rippleContainerBgColor,
                   0 0 ($rippleSize/($rippleSize/($rippleSize/20)))+px ($rippleSize + ($rippleSize/8))+px transparent;
      }
    }
  }
}

 

CSS styles

In case you’re not using SASS, here’s the CSS output from the mixin:

.ripple-wrapper {
  position: relative;
  z-index: 0;
}

.ripple-wrapper:before,
.ripple-wrapper:after,
.ripple-wrapper > *:first-child:before,
.ripple-wrapper > *:first-child:after {
  content: '';
  display: block;
  position: absolute;
  top: 25%;
  left: 50%;
  width: 2px;
  height: 2px;
  border-radius: 2px;
  -webkit-animation: ripple 4s infinite;
          animation: ripple 4s infinite;
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
}

.ripple-wrapper:before {
  z-index: -4;
  margin-left: 5px;
  -webkit-animation-delay: 0;
          animation-delay: 0;
}

.ripple-wrapper:after {
  z-index: -2;
  margin-left: 8px;
  -webkit-animation-delay: .5s;
          animation-delay: .5s;
}

.ripple-wrapper > *:first-child:before {
  z-index: -3;
  margin-left: -3px;
  -webkit-animation-delay: .3s;
          animation-delay: .3s;
}

.ripple-wrapper > *:first-child:after {
  z-index: -1;
  margin-left: -8px;
  -webkit-animation-delay: .8s;
          animation-delay: .8s;
}

.ripple-wrapper:hover:before,
.ripple-wrapper:hover:after,
.ripple-wrapper > *:first-child:hover:before,
.ripple-wrapper > *:first-child:hover:after {
  -webkit-animation-play-state: paused;
          animation-play-state: paused;
}

@-webkit-keyframes ripple {
  0% {
    box-shadow: 0 0 0 0 transparent,                    0 0 0 0 transparent,                    0 0 0 0 transparent,                    0 0 0 0 transparent;
  }
  5% {
    box-shadow: 0 0 0 0 #009cff, 0 0 0 0 rgba(255, 255, 255, 0.5), 0 0 0 0 #009cff, 0 0 0 0 rgba(0, 0, 0, 0.2);
  }
  75% {
    box-shadow: 0 0 37.5px 75px #009cff, 0 0 10px 78.75px transparent, 0 0 25px 78.75px #009cff, 0 0 3.75px 84.375px transparent;
  }
}

@keyframes ripple {
  0% {
    box-shadow: 0 0 0 0 transparent,                    0 0 0 0 transparent,                    0 0 0 0 transparent,                    0 0 0 0 transparent;
  }
  5% {
    box-shadow: 0 0 0 0 #009cff, 0 0 0 0 rgba(255, 255, 255, 0.5), 0 0 0 0 #009cff, 0 0 0 0 rgba(0, 0, 0, 0.2);
  }
  75% {
    box-shadow: 0 0 37.5px 75px #009cff, 0 0 10px 78.75px transparent, 0 0 25px 78.75px #009cff, 0 0 3.75px 84.375px transparent;
  }
}

About the Author:

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>