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;
  }
}

48in48

IMG_2414

Over the weekend, I participated in a hackathon with a great mission–to build websites for 48 Atlanta-area non-profits in 48 hours. My team of five people made four websites in just two days! It was a great experience, and I’m so glad I got to put my skills to use for a good cause.

To close out the weekend, all the websites were judged by a panel of non-profit leaders from the community. The winning 3 websites had an extra donation made to their organizations. I’m proud to say that our team won first place for the website we made for Raising Expectations! I’m so honored to have been a part of the winning team, and even happier to have been able to make a contribution to so many worth organizations.

On “unicorns”

I’ve never been sure of my true title in the web world. “Designer” and “Developer” both feel incomplete. I recently discovered Brad Frost’s post on the topic, and I feel reassured that I’m not alone. There’s a lot of overlapping territory between the two disciplines, and that’s terrain I am proud to inhabit. By embodying both a designer and a developer, I get to skip a lot of overhead in terms of design documentation, and I also don’t produce designs that are impossible to implement. Don’t get me wrong–I think there’s inherent value in disregarding everything you know about tradition, convention, and feasibility during certain parts of the design process…as in, the very start. The rest of the process is about honing and refining, and in the process figuring out just how you’re going to get the boat afloat. And although I am adamantly opposed to defining design as “making things pretty,” it doesn’t hurt to be able to make aesthetic improvements on-the-fly as a programmer.

I’m not sure that “design-eloper” or “unicorn” will take off as a generalized discipline. It seems too easy to fall into the “jack of all trades, master of none” trap. I also buy the argument for specialized disciplines–it just depends on the team. However, as an individual pursuit, I believe in us hybrids. I am simultaneously passionate about good design and quality code–to me it feels like they’re ultimately the same concept. So it is with this spirit and passion that I proudly embrace a new label: Kim Bryant, UX Unicorn.

kb_unicorn

On Bootstrap’s Grid: Using display: inline-block instead of floats

I had the opportunity to use Twitter’s Bootstrap on a project recently, and I’ve been really impressed by how well their grid system is built. I used the LESS version of the Bootstrap UI, which is amazingly flexible. My main grievance is that they’ve chosen to structure the basic grid by using CSS floats.

The problem with floats

The problem with using CSS floats comes when you have grid elements of varying, or unpredictable height. In the example below, the divs should line up in a 4-column grid, but because the first item in column 3 is too tall, what should be the bottom item in column 1 actually gets stuck behind the too-tall top item from column 3. Now the alignment on the second and third rows is all messed up.

1
2
3
4
1
2
3
4
1
2
3

Using display: inline-block instead

Fortunately, there’s another method we can use to get our grid lined up the way we want it–display: inline-block! The only catch is that inline-block elements natively render with a small space between each item. So if you want to use exact measurements for your grid, 25% width for each item in a 4-column grid, you’ll notice that the last column drops to form a new row because of the space added between the inline-block elements.

1
2
3
4

There are a number of ways to get rid of this space, but I think most of them are a bit too hack-y to be ideal solutions. It seems like the easiest solution is to remove the whitespace between elements in the HTML, e.g., <li>A list item</li><li>A second list item</li>

Getting it just right

However, if your grid elements are dynamically generated, it may not always be possible to create this HTML structure. Therefore, my personal favorite method to remove the whitespace is to apply a negative margin around each grid element. By default, the added whitespace is about .25em wide. So, I just apply margin: 0 -0.125em; to each grid element, and, voilá, the grid looks just right!

With inline-block, we can also vertically-center any dangling items. Say, for instance, your last row only has 3 items. If it suits your design, you can easily center those 3 items in the row by applying text-align: center to the parent container. Ultimately, if you’re going to have elements that vary extremely in height, you might also want to use the Masonry Javascript plugin to avoid big, uneven gaps in the vertical rhythm.

1
2
3
4
1
2
3
4
1
2
3

Implenting the override

To implement my display: inline-block override, I just redefined these methods in a new file, overrides.less. By making the overrides in a new file instead of overwriting the Bootstrap source code, I can more easily update the Bootstrap source without losing my changes.

.make-xs-column() { float: none; display: inline-block; margin: 0 -0.125em; }
.make-sm-column() { @media (min-width: @screen-sm-min) { float: none; display: inline-block; margin: 0 -0.125em; } }
.make-md-column() { @media (min-width: @screen-md-min) { float: none; display: inline-block; margin: 0 -0.125em; } }
.make-lg-column() { @media (min-width: @screen-lg-min) { float: none; display: inline-block; margin: 0 -0.125em; } }