HOW TO: create the ripple effect from Material Design
I was reading through the Material Design spec today and thought “how would I create that ripple type effect for user interaction from scratch?”
For those in camp TL;DR here’s some code and a demo!
What’s Material Design?
It’s a new design spec from Google and it’s pretty nice IMO.
For those not in the mood for videos check out google.com/design/spec/material-design
The ripple effect
“Where’s the ripple effect?” you might ask.
Google has specified a nice section regarding responsive interaction with particular concern to user input. That can been seen here. Note the nice demo animations.
Enough already, let’s make it!
DOM
I’ve opted to implement the effect we are aiming for with a “ripple” attribute. The demo will use buttons.
<button ripple>Interact with me</button>
Thinking about the effect. We need some form of container for our ripples.
<button ripple>
Interact with me
<div class="ripple--container"></div>
</button>
Each ripple will be a span element within the container.
We could do this with CSS pseudo elements but the effect won’t be as anticipated as we can only use one instance of :before/:after at a time.
The solution we are going to use is that on mousedown the ripple effect will fire from the point where our user clicks within a button. This involves creating a span whose default styling will animate the ripple.
Imagine a user spam clicks our button, the effect would be similar to
<button ripple>
Interact with me
<div class="ripple--container">
<span style="top: 5px; left: 20px;"></span>
<span style="top: 5px; left: 20px;"></span>
<span style="top: 5px; left: 20px;"></span>
<span style="top: 5px; left: 20px;"></span>
</div>
</button>
JS
Think what we need to do with our script.
- Add the ripple container to our buttons.
- Upon mousedown, add spans with the correct positioning to that container.
- Debounce the mouseup event to allow our ripple effect to fully animate before cleaning out all the span tags from our buttons.
(Note; I’ve implemented a simple debounce function for the code which I won’t cover here but you could use something like lodash for this. If you’re unfamiliar with debouncing a function call, you can check out some info here.)
var ripples = document.querySelectorAll(‘[ripple]’),
rippleContainer,
ripple;for (i = 0, len = ripples.length; i < len; i++) {
ripple = ripples[i];
rippleContainer = document.createElement('div');
rippleContainer.className = 'ripple--container';
ripple.addEventListener('mousedown', addRipple);
ripple.addEventListener('mouseup' , debounce(cleanUp, 2000));
ripple.rippleContainer = rippleContainer;
ripple.appendChild(rippleContainer);
}
We iterate over our buttons and add the ripple container to each, binding our mousedown/mouseup events.
addRipple = function(e) {
var ripple = this,
size = ripple.offsetWidth,
pos = ripple.getBoundingClientRect(),
rippler = document.createElement('span'),
x = e.clientX - pos.left - (size / 2),
y = e.clientY - pos.top - (size / 2),
style = 'top:' + y + 'px; left:' + x + 'px; height: '
+ size + 'px; width: ' + size + 'px;';
ripple.rippleContainer.appendChild(rippler);
rippler.setAttribute(‘style’, style);
};
Our addRipple function grabs the element position and uses this in conjunction with the click coordinates to calculate the new span positioning within the button.
For example, if a user clicked in the middle of a 100 by 100px button, the span positioning would be 50px both top and left.
cleanUp = function() {
var container = this.rippleContainer;
while (this.rippleContainer.firstChild) {
container.removeChild(container.firstChild);
}
};
The cleanUp function will clear out the span elements once the last ripple animation has finished. This is ensured via the use of a debounce.
CSS
The styling is relatively simple. We just need to ensure that our container and button will contain our animated rippling span elements and that our spans produce the correct ripple effect.
[ripple] {
position: relative;
overflow: hidden;
}
[ripple] .ripple--container {
position: absolute;
top : 0;
right : 0;
bottom : 0;
left : 0;
}
[ripple] .ripple--container span {
transform : scale(0);
border-radius : 100%;
position : absolute;
opacity : 0.75;
background-color: #fff;
animation : ripple 1000ms;
}
@keyframes ripple {
to {
opacity : 0;
transform: scale(2);
}
}
Span elements will start off at a scale size of 0 and the animation will scale them up to double their size.
To conclude
Visual feedback for users when they interact with your applications can be effective.
We’ve had a look at a solution for creating the ripple effect for responsive user input feedback.
For some, including myself, it’s easier to see some code in action, so feel free to check out a demo:
As always, any questions or suggestions, please feel free to leave a response or tweet me 🐦!