CSS Techniques for Material Ripple Effect

A guide into different techniques for the ripple effect using CSS and JavaScript

I recently had to implement the ripple effect from material design into a web application. And then I realized I had no idea how that was implemented. This led me into a journey to study existing implementations, and even come up with a brand new technique that might be useful to you.

What is this ripple effect?

Wait, you don’t know the ripple effect from Google’s Material Design? Have you been living on a cave for how many years?

The ripple effect is used when you press a button. It works the same way for mouse or touch interactions.

The position you click or touch on the button is called the point of contact. From there, a ripple is sent moving outwards, losing opacity as it grows larger until it fills the entire button. Then it disappears completely.

The dynamics of this ripple effect are similar to the ripples you get when you touch a liquid surface, or when you drop a rock into a lake.

The ripples you will find on the web

After doing some research, I could find two main techniques that are used for implementing the ripple effect on web applications.

Using ::after pseudo-element

Using this technique, the ::after pseudo-element of the button is styled as a semi-transparent circle, and animated to grow and fade. The container button needs to have overflow: hidden so that the circle never overflows outside of the button’s surface, and position: relative to make it easy to position the circle within the button. You can read more details of this technique on this article by Ionuț Colceriu.

Example of ripple effect using pseudo-element.

One of the great things about this technique is that it’s a pure CSS solution to the ripple effect. However, the ripple effect always starts from the center of the button, instead of the point of contact. That’s not the most natural feedback.

It could be improved by using JavaScript to store the point of contact, and use it to position the ripple. That’s exactly what material.io has done for their web ripple component. It uses CSS variables to store the point of contact, and the ::after pseudo-element uses these variables for positioning.

Using child elements

In essence, this technique uses the same strategy as before. But instead of a pseudo-element, it adds a span element inside of the button, which can then be positioned through JavaScript. This technique is described on this article by Jhey Tompkins.

The simplest implementation creates a span for each click on the button, and uses the mouse position on the click event to change the position of the span. A CSS animation makes the span grow and fade until becoming fully transparent. We can choose to remove the span from the DOM once the animation finishes, or just leave it there under the carpet — no one will really notice a transparent span hanging around.

Using spans inside a button for the ripple effect.

I did find another variation of this, on which the child element is an svg instead of a span, and the svg is animated through JavaScript. This variation is explained by Dennis Gaebel, but in essence it seems to be the same, and perhaps allowing to use complex SVG shapes and effects.

A problem with submit inputs

Both techniques described above seem great. But this is what happens when I tried to apply them on input elements with type=submit:

Ripple effect doesn’t work when applied to input[type=submit].

Why don’t they work?

The input element is a replaced element. In short, that means that there’s very little you can do with those elements, with respect to DOM and CSS. Specifically, they can’t have child elements, and no pseudo-elements either. Now it’s clear why these techniques fail.

So, if you’re using Material Design, it’s better to stay away from input[type=submit], and stick to button elements. Or just keep reading.

Adding ripples to submit inputs

On the web application I was working on, we already had lots of submit buttons. Changing all of them to become a different element would require a lot of work, and a high risk of breaking stylesheets and JavaScript logic. So I had to figure out how to add ripples to the existing submit buttons.

Using a wrapping container

I quickly realized that I could wrap the submit button inside of an inline-block element, and use the inline-block element as the ripple surface. Here’s a quick demo:

Using a wrapping container to have a ripple effect on input[type=submit].

While I do like this solution for its simplicity, it still required me to change the markup in too many places. And I knew it would be a fragile solution — new developers would come into the project, and create submit buttons without properly wrapping them in a ripple surface. So I kept searching for other solutions that didn’t require changing the DOM.

Radial gradients

The radial-gradient syntax allows me to control both the center and the size of the gradient. Of course, it also allows me to control the color of the gradient, including semi-transparent colors. And it never ever overflows the element it’s applied to. So it seems like it already does everything I need!

Not so fast… there is one thing missing: the background-image property is not animatable. I could not make the gradient grow and fade to transparent using CSS animations. I did manage to make it grow by animating the background-size property, but that was all I could do.

An attempt to create the ripple effect using a background gradient.

I tried a few other things, such as having a fading circle as an animated image (using the apng format), and applied it as a background-image. But then I could not control when the image loop started and ended.

Finally, a solution with JavaScript

What you can’t do in CSS, you can do it in JavaScript. After spending more time than I’m willing to admit trying to get this effect working using CSS animations, I just gave up and decided to write the animation in JavaScript.

I started with the radial gradient solution above, and used window.requestAnimationFrame to make a fluid animation of the radial gradient, growing and fading. Here’s my final solution:

Mission accomplished: ripple effect working on a submit button.

Conclusion

So it is possible to have ripple effects on submit buttons, just not with CSS alone.

I couldn’t find this technique documented anywhere in the web, so I’m calling it my own. Leonardo’s ripple technique does not require changes to DOM, and works for any element because it doesn’t rely on pseudo-elements or child elements. However, it’s not a flawless solution.

First, there’s performance. By animating the gradient with JavaScript, you lose a lot of browser optimizations. But, because the only property being changed is the background-image, I would suspect that browsers would not need to reflow, and would just require reapplying the styles and repainting the element. In practice, that is exactly what happens, and performance is really good. The exception to that statement is Firefox Mobile, which for some reason does not keep up with the animation. (edit: animation is smooth on modern Firefox Mobile versions)

Second, the technique uses the background-image property of the button. If your design requires your buttons to have an image applied to its background, the ripple effect would override that. If you really need that image on your design, then the JavaScript could be modified to draw the radial gradient on top of the existing background image.

Third, this does not seem to work in Internet Explorer. However, I don’t see any reason why it shouldn’t work with IE10 and above. Maybe it’s because IE uses a different syntax for radial-gradient. But, who cares about IE now-days? (edit: this method works without any issues on Internet Explorer 11).

Leonardo Fernandes Head of Delivery Connect with Leonardo via LinkedIn

A selection from our recent work