Category Archives for Demos

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

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