Today I was working in Google Chrome Canary, clicked on a link in my bookmark bar, and saw a pretty cool effect.
The link filled in slowly with a darker color starting where my cursor clicked. I thought it was pretty dope and decided to make it myself.
Doing a little research into the subject it seemed like most other people trying to get the desired effect were adding a span element inside of that element and then animating that span. Sounded like a good space to start.
Started working on an on click function to add a span that was centered on where the click occurred. The three variables that need to be taken into account here are: 1. The location on the page of the click. 2. The position on the page of the button that was clicked. 3. The size of the span.
Since I was going to be calling my function from a ‘mouseup’ event listener on the button, the location of the click, and the button position on the page are super easy to get.
The function’s event object will have pageX and pageY properties that will describe the location ot the click.
The this object of the function will refer to the button, and it’s properties offsetTop and offsetLeft will describe the position of the button clicked on the page.
For my first pass I decided to just make the span a white circle with 50px diameter, and worry about making it dynamic later.
Netting me this:
While it might not be the most important part of the ripple, the fact that my span was currently solid white was extremely annoying to me, so my next task was changing that. I added an “opacity: 1” property to my CSS class, as well as a transition property “all 2s”. Then I made a new CSS class, cleverly named ripple, that was just “opacity: 0”.
In order for the transition to work, the element’s opacity had to be 1 and then “transition” to 0. I thought this could be accomplished simply in my event handler with:
rippleSpan.className = 'clickRippleSpan'; this.appendChild(rippleSpan); rippleSpan.className += ' ripple';
But I was incorrect.
I had to wait for the page to “paint” the span with just the clickRippleSpan class, and then add the ripple class. This was accomplished easily using requestAnimationFrame.
Obviously, a 50px diameter circle wouldn’t work for very large buttons, so I had to come up with a better strategy there. I went with an extremely mathematical route… and set the span’s diameter to the max(widthOfButton, heightOfButton) * 2. It’s not exactly perfect, but with the fade out and everything, I think it works pretty well.
Having the diameter set like that though, meant that the buttons now looked like they were flashing when I was clicking them, rather than having a ripple go through them. Quite the problem. So, it was time to add dynamic sizing to the ripple.
I thought the easiest way to do this would be by going from transform: scale(0) to transform: scale(1). So that’s what I did, by adding those CSS properties to the clickRippleSpan and the ripple CSS classes.
Really starting to take shape here:
Removing Completed Ripples
I did not want my buttons to become cluttered with useless spans, if for no reason other than if someone inspected the element, so I tried to think of a way to delete the ripple span once it’s job was done.
Admittedly, my first thought was to use a setTimeoutFunction. I knew when the transition was going to start, and I knew how long it was going to last. Seemed like it would be quite easy to implement that way, and it was… but I didn’t like it.
I ended up putting an event listener on the span that listened for the transitionend event to fire. I thought it was cleaner that way.
Coming along nicely:
Material Design says that:
Rather than using a single duration for all animations, adjust each duration to accommodate the distance travelled, an element’s velocity, and surface changes.
So I thought why not make the duration of my ripple dependent on the size of the button. Should be easy enough?
I’m already determining the diameter dynamically, I’ll just divide the diameter by say… 250… and set the transition time to that.
Whelp, seems good enough to me for now. I’ve uploaded it to GitHub, if you want to check it out, or see if I’ve made any improvements.