Showcase

The Descent Ripple Effect

By Michael Lucht
Keywords: UI, Ripple effect, Svelte, React, SVG
January 12, 2021

The descent-ripple package provides an easy way to add ripple animations to buttons and button-like UI elements. That means when you click on a button, or whatever UI-component you add this effect to, a short animation will start at the position of the click, which is bound by the borders of the button. This looks nice and also gives the user the feel of a responding UI. It is especially helpful for touch interfaces since it gives the user some feedback about the click position.

Getting started

>

For React there is also a conveniently wrapped component available, see react-descent-ripple

The package can be used with any javascript framework, or even without. Here I will show you how to use it with React, Svelte, or without Framework. The package is written using Svelte and is already compiled and minimized, so there is no overhead or requirement.

You can import the library using:

npm install descent-ripple

Then, depending on your framework, you can add the ripple:

// MyButton.svelte <script> import ripple from 'descent-ripple'; let rippleOptions = {}; </script> <button use:ripple={rippleOptions}>click me </button>
// MyButton.tsx import React from 'react'; import ripple from 'descent-ripple'; export const MyButton = ()=>{ const buttonRef = React.useRef<Node>(null); React.useEffect(()=>{ if (buttonRef.current){ let rippleOptions={}; const buttonRipple = ripple(buttonRef.current,rippleOptions); return buttonRipple.destroy; } },[]); return ( <button ref={buttonRef}>click me</button> ) }
// index.html <html> ... <head> ... <script src="node_modules/descent-ripple/dist/index.js"></script> ... </head> <body> <button>click me</button> <script type="text/javascript"> var ripple = window["descent-ripple"]; var buttons = document.getElementsByTagName("button"); var rippleOptions = {}; for (let i = 0; i < buttons.length; i++) { ripple(buttons[i],rippleOptions); } </script> </body> </html>

That's all you need to add the effect and all the examples below just adjust the rippleOptions object.

>

The ripple function usually adds the needed CSS to your button. If the result is not well-positioned, it could be that your framework is overriding the style property of your button/component. Try to manually add "position:relative" to the style of your button, if the ripple is not in place, and "overflow:hidden", if the ripple draws out of boundaries.

Options

nLines and nCircles

The ripple animates lines and/or circles. With nLines you say, how many lines you want. Lines are spaced equally around the center. You can have both circles and lines. More than one circle will make sense later when we know how to use delay.

also
click
me
rippleOptions =
{

nLines: 3,

nCircles: 0,

}

lineProps and circleProps

Lines and circles are svg-path and svg-circle elements. Colors are defined by the stroke and fill property. Other interesting attributes, especially for the lines, are stroke-linecap, stroke-linejoin, stroke-miterlimit, stroke-opacity, stroke-width. You can apply the same props to all circles and lines, or handle each individually. To do that, supply a function of the index:

also
click
me
rippleOptions =
{

nLines: 6,

nCircles: 1,

circleProps: {

stroke: 'red',

fill: 'rgba(222,2,2,.1)',

"stroke-width:" 60,

},

lineProps: (i) => {

fill: 'none',

stroke: 'hsl(' + ( 12 * i ) + ',90%,50%)',

"stroke-width": 40

},

}

lineBreakDist and rotation

Each path is split into several segments via lineBreakDist. A larger distance means fewer breaks. With rotation, you can make the lines bend circularly. You can supply a single value or a function on the line-break index. rotation is measured in degrees.

also
click
me
rippleOptions =
{

nLines: 6,

nCircles: 0,

lineProps: (i) => {

fill: 'none',

stroke: (i % 2)===0 ? 'black' : 'white',

"stroke-width": 40

},

rotation: (i)=>10 * i,

lineBreakDist: 10,

}

Animation and AnimationProps

By default descent-ripple supports the following animation types from Svelte: "draw", "scale", "fade", "blur" and "fly" plus "draw-reverse". All these animations can take props, which typically include {delay:number, duration:number, easing: (t:number)=>number}, you can look them up on Svelte. "draw-reverse" accepts the same props as "draw". You can change in and out animations for circles and lines and the props can be passed as a function of the circle/line index.

also
click
me
rippleOptions =
{

nLines: 4,

nCircles: 0,

lineProps: (i) => {

fill: 'none',

stroke: (i % 2)===0 ? 'black' : 'white',

"stroke-width": 60

},

rotation: (i)=>0 * i,

lineBreakDist: 0,

linesOutAnimation: ''draw-reverse',

linesOutAnimationProps: (i) => ({

duration: Math.max(20, 700 - 120 * i),

delay: 120 * i

}),

}
also
click
me
rippleOptions =
{

nLines: 0,

nCircles: 4,

circleProps: {

stroke: 'red',

fill: 'rgba(222,2,2,.05)',

"stroke-width:" 60,

},

circleOutAnimation: ''scale',

circleInAnimationProps: (i) => ({

duration: Math.max(20, 500 - 100 * i),

delay: 100 * i

}),

circleOutAnimationProps: (i) => ({

duration: Math.max(20, 900 - 100 * i),

delay: 100 * i

}),

}

Randomness and timeToRemove

With the randomness option you can distort the line-forming randomly. This can add a more interesting look. Also, you can adjust the time point when the out animation should start with timeToRemove. Default is 500 milliseconds.

also
click
me
rippleOptions =
{

nLines: 10,

nCircles: 1,

circleProps: {

stroke: 'black',

fill: 'red',

"stroke-width:" 60,

},

lineProps: (i) => {

fill: 'none',

stroke: 'red'

"stroke-width": 10

},

rotation: (i)=>0 * i,

timeToRemove: 900,

randomness: 1.5,

lineBreakDist: 20,

linesInAnimationProps: (i) => ({

duration:800,

easing: t=>t

}),

linesOutAnimationProps: (i) => ({

duration: Math.max(20, 700 - 120 * i),

delay: 120 * i

}),

circleInAnimationProps: (i) => ({

duration: Math.max(20, 500 - 100 * i),

delay: 100 * i

}),

circleOutAnimationProps: (i) => ({

duration: Math.max(20, 900 - 100 * i),

delay: 100 * i

}),

}

backstep

With the backstep option you can increase the edginess of the lines. It should usually be between 0 and 1. Glass UI designs are trendy right now, how about a cracking effect?

also
click
me
rippleOptions =
{

nLines: 11,

nCircles: 0,

lineProps: (i) => {

fill: 'none',

stroke: 'black'

"stroke-width": 2

},

rotation: (i)=>0 * i,

timeToRemove: 900,

randomness: 1.5,

lineBreakDist: 10,

backstep: 0.3,

linesInAnimationProps: (i) => ({

duration:800,

easing: crackEasing()

}),

}

Here is the code for crackEasing:

const crackEasing = () => { const n = 3; const steps = Array(n).fill().map(() => 0.1 + Math.random()); const stepsLength = steps.reduce((p,v)=>p+v,0); const findheight = (t)=>{ if (t < .05){ return t / .05; } let p = 0, q = 0; for (let i = 0; i < n; i++){ q += steps[i] / stepsLength p += (i+1); if (t < q - .05){ return p; } if (t < q){ return p + (1 - (q-t)/.05)*(i+2) ; } } return (n+3)*(n+2)/2; } return (t) => 2 * findheight(t) / ((n+3)*(n+2)); };

Custom animation

The in and out animations also allow passing a custom animation. You can get some information about this in the Svelte tutorial. Javascript animations also work, use the tick.

also
click
me
rippleOptions =
{

nLines: 20,

nCircles: 0,

lineProps: (i) => {

fill: 'none',

stroke: (i % 2)===0 ? 'black' : 'white',

"stroke-width": 1

},

rotation: (i)=>0 * i,

timeToRemove: 400,

randomness: 2.5,

lineBreakDist: 30,

backstep: 0.03,

linesInAnimation: electroshock,

linesOutAnimation: electroshock,

linesInAnimationProps: (i) => ({

duration:400,

easing: t => Math.random()

}),

linesOutAnimationProps: (i) => ({

duration: Math.max(20, 700 - 120 * i),

delay: 120 * i

}),

}

This is the custom animation:

const electroshock = (node, { duration, delay, easing }) => { return { duration, delay: delay ? delay : 0, easing: easing ? easing : (t) => t, css: (t) => { return ` stroke: rgba( ${Math.round(t * 255)}, ${Math.round(t * 255)}, ${Math.round(t * Math.random() * 255)}, ${t} );`; }, }; };

Conclusion

There are a ton of other possiblities to style the ripple effect. Feel free to use it on your page and if you like (fully optional) you can send me a link to ripple@gradientdescent.de. Also let me know, if you have improvement ideas via github. Thanks!

Share this article on twitter - Vote on next topics - Support me

This might also be interesting for you:

Support me:

or directly

Design & Logo © 2020 Michael Lucht. All rights reserved.