material-design-ripple
v2.0.1
Published
Zero-dependency material ripple animation library
Maintainers
Readme
material-design-ripple
A single function that adds the material ripple animation to any element you point at. No dependencies, ~2 kB gzipped.
import { ripple } from 'material-design-ripple'
ripple('.button, .card')Demo
Install
npm install material-design-rippleUsage
Pass a selector and the effect is delegated from the document, so elements rendered later are covered too -- no need to re-run anything after a render:
import { ripple } from 'material-design-ripple'
const handle = ripple('.button')
document.body.append(newButton) // ripples, without another ripple() call
handle.destroy() // removes every listener, rule and live waveElements and node lists are bound directly instead, and the two can be mixed:
ripple(document.querySelector('#cta'))
ripple(document.querySelectorAll('.chip'))
ripple(['.card', myButton])With a plain script tag:
<script src="https://unpkg.com/material-design-ripple/dist/material-ripple.min.js"></script>
<script>
MaterialRipple.ripple('.button')
</script>Options
ripple('.icon-button', { center: true, color: 'currentColor' })| Option | Default | What it does |
| -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| color | 'auto' | 'auto' composites the background behind the host and picks black or white. Any CSS colour works, including 'currentColor'. |
| opacity | 0.3 | Opacity the wave holds while pressed. |
| center | false | Emanate from the centre instead of the pointer. Right for icon buttons and FABs. |
| duration | 350 | Expansion duration, in ms. |
| fadeDuration | 400 | Fade-out duration once released, in ms. |
| keyboard | true | Also ripple on Space / Enter activation, centred. |
| clip | true | Applies position: relative and overflow: hidden to hosts for you. |
Notes
The public API is ripple and nothing else. Everything under src/ beyond
the . export is internal and can change in a patch release. Changes are
recorded in CHANGELOG.md.
Press and release are separate phases. The wave expands on press and holds
until the pointer lifts, then fades -- so a long press stays lit instead of
draining away under your finger. Release is tracked on window, so dragging off
the element before letting go still ends the ripple.
Clipping is handled for you. clip: true installs one
:where(...) rule per call, which has zero specificity: any declaration of your
own overrides it. Set clip: false to opt out entirely.
Colour is measured, not guessed. color: 'auto' walks up the ancestors
compositing background colours until it has an opaque one, so translucent
surfaces resolve to what you actually see. Colours outside sRGB (oklch(),
lab(), color(display-p3 ...)) are skipped rather than misread, and the next
ancestor is consulted instead.
Reduced motion is respected. With prefers-reduced-motion: reduce the wave
appears without travelling, and still fades on release, so the press keeps its
feedback without the movement.
One wave per press. Rapid presses overlap rather than cutting each other off, and multi-touch gives each pointer its own wave.
A <span> is appended to each host. It is aria-hidden and out of flow, but
it does become the host's last child -- so a :last-child selector aimed at your
own children will no longer match. This is the same trade-off the Material
Components implementation makes.
Nested hosts. With delegation the innermost matching ancestor wins, so a
button inside a rippling card ripples alone. Unlike pre-1.0 this is done without
stopPropagation(), so your own pointerdown listeners still fire.
Two ripple() calls matching the same element give it two waves. Scope your
selectors so they do not overlap, or pass options to a single call.
Selectors are validated when you call ripple(). An invalid one throws a
TypeError naming it, rather than failing later on each press. Do not build a
selector out of untrusted input: it is validated, not sanitised, and a selector
API is the wrong place to put user data.
SSR is safe to import; ripple() itself needs a DOM, so call it on the client.
Migrating from 0.1.x
set() is gone. It is a straight rename, plus a handle you can now hold on to:
- import { set } from 'material-design-ripple'
- set(['.card', '.button'])
+ import { ripple } from 'material-design-ripple'
+ ripple(['.card', '.button'])Three behaviours changed underneath, all of them deliberate:
- Presses hold until release instead of fading from the moment you press.
stopPropagation()is no longer called, so your ownpointerdownlisteners on ancestors fire again.- Hosts are clipped for you, so you can drop the
overflow: hiddenyou were keeping around for the effect. Passclip: falseto go back to doing it yourself.
Selectors are also delegated now rather than resolved once, which is why
elements rendered after the call are covered. If you were re-running set()
after every render, you can stop.
Developing
npm run dev| Script | Does |
| -------------------- | --------------------------------- |
| npm run dev | Serves the demo with hot reload |
| npm test | Runs the test suite |
| npm run build | Builds the library into dist/ |
| npm run build:demo | Builds the demo site into site/ |
| npm run format | Formats everything with Prettier |
License
MIT © Viola Péter
