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
object.
rippleOptions
>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
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.
nLines
nLines: 3,
nCircles: 0,
lineProps and circleProps
Lines and circles are svg-path and svg-circle elements. Colors are defined by the
and stroke
property. Other interesting attributes, especially for the lines, are fill
, stroke-linecap
, stroke-linejoin
, stroke-miterlimit
, stroke-opacity
. You can apply the same props to all circles and lines, or handle each individually. To do that, supply a function of the index:
stroke-width
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
, 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.
rotation
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"
and "blur"
plus "fly"
. All these animations can take props, which typically include "draw-reverse"
, you can look them up on Svelte. {delay:number, duration:number, easing: (t:number)=>number}
accepts the same props as "draw-reverse"
. 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.
"draw"
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
}),
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
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 randomness
. Default is 500 milliseconds.
timeToRemove
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
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?
backstep
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
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:
- How to Implement Custom React Hooks in Svelte
- React Hooks in Svelte
- tree-shake-css - project page
- Sveltes tick is my new best friend - porting react-textfit to Svelte
- How to merge cells with svelte-window
- Porting React to Svelte - From react-window 1:1 to svelte-window
- Poll: Which React library would you love to see in Svelte?
- Porting React components to Svelte - It might be easier than you think
- The React Descent Ripple Effect - Showcase
- Svelte and Typescript
- How this blog is made - Basic Structure, Elder and Markdown
- How this blog is made part 2 - Make your website sketchy with Tailwind and Roughjs
- Why are component libraries so complicated with React compared to Svelte?