Showcase

The React Descent Ripple Effect

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

The descent-ripple package provides an easy way to add ripple animations to buttons and button-like UI elements and react-descent-ripple is a conveniently wrapped component. 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

Usage with React is easy. 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 react-descent-ripple

Then, you can add the ripple:

// MyButton.tsx or MyButton.jsx import React from 'react'; import Ripple from 'react-descent-ripple'; export const MyButton = ()=>{ return ( <button> <Ripple/> click me </button> ) }

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

>

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
<Ripple

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
<Ripple

nLines={6}

nCircles={1}

circleProps={{!p!}}

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
<Ripple

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
<Ripple

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
<Ripple

nLines={0}

nCircles={4}

circleProps={{!p!}}

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
<Ripple

nLines={10}

nCircles={1}

circleProps={{!p!}}

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
<Ripple

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
<Ripple

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.