Research
Security News
Threat Actor Exposes Playbook for Exploiting npm to Build Blockchain-Powered Botnets
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
@material/ripple
Advanced tools
The Material Components for the web Ink Ripple effect for web element interactions
@material/ripple is a package from the Material Design Components (MDC) library that provides a ripple effect for interactive elements. This effect is commonly used in Material Design to indicate the point of touch or click, giving users visual feedback.
Basic Ripple
This code initializes a basic ripple effect on a button element with the class 'mdc-button'.
const MDCRipple = require('@material/ripple').MDCRipple;
const buttonRipple = new MDCRipple(document.querySelector('.mdc-button'));
Unbounded Ripple
This code initializes an unbounded ripple effect, which means the ripple effect will extend beyond the bounds of the element.
const MDCRipple = require('@material/ripple').MDCRipple;
const unboundedRipple = new MDCRipple(document.querySelector('.mdc-ripple-surface'));
unboundedRipple.unbounded = true;
Ripple with Custom Color
This code initializes a ripple effect with a custom color on a button element. The ripple color is set to a semi-transparent red.
const MDCRipple = require('@material/ripple').MDCRipple;
const customRipple = new MDCRipple(document.querySelector('.mdc-button'));
customRipple.root.style.setProperty('--mdc-ripple-color', 'rgba(255, 0, 0, 0.3)');
react-ripple is a lightweight React component that provides a ripple effect similar to @material/ripple. It is easy to use and integrates well with React applications. However, it may not offer as many customization options as @material/ripple.
vue-ripple-directive is a Vue.js directive that adds a ripple effect to elements. It is specifically designed for Vue.js applications and offers a simple way to add ripple effects. Compared to @material/ripple, it is more tailored to Vue.js but may lack some advanced features.
ripple-js is a standalone JavaScript library that provides a ripple effect for any web application. It is framework-agnostic and can be used with plain JavaScript, React, Vue, or any other framework. It offers flexibility but may require more manual setup compared to @material/ripple.
MDC Ripple provides the Javascript and CSS required to provide components (or any element at all) with a material "ink ripple" interaction effect. It is designed to be efficient, uninvasive, and usable without adding any extra DOM to your elements.
MDC Ripple also works without javascript, where it gracefully degrades to a simpler CSS-Only implementation.
In order to function correctly, MDC Ripple requires a browser implementation of CSS Variables. MDC Ripple uses custom properties to dynamically position pseudo elements, which allows us to not need any extra DOM for this effect.
Because we rely on scoped, dynamic CSS variables, static pre-processors such as postcss-custom-properties will not work as an adequate polyfill (...yet?).
Edge and Safari, although they do support CSS variables, do not support MDC Ripple. See the respective caveats for Edge and Safari for an explanation.
npm install --save @material/ripple
For many components, providing a ripple interaction is straightforward.
Let's say we have a surface
element that represents a basic surface.
<div class="surface" tabindex="0">
<p>A surface</p>
</div>
We also have some basic styles for our surface that use mdc-elevation to raise it up off of its background.
@import "@material/elevation/mixins";
.surface {
@include mdc-elevation(2);
position: relative;
border-radius: 2px;
text-align: center;
/* Indicate to user element is interactive. */
cursor: pointer;
To add a ripple to our surface, first we include the proper Sass mixins within our surface's styles. We also add a few additional properties that ensure the ripple's UX is correct.
@import "@material/elevation/mixins";
@import "@material/ripple/mixins";
.surface {
@include mdc-ripple-base;
@include mdc-ripple-bg((pseudo: "::before"));
@include mdc-ripple-fg((pseudo: "::after"));
// ...
/* "Bound" the ripple, preventing the pseudo-elements from bleeding out of the box. */
overflow: hidden;
}
This code sets up .surface
with the correct css variables as well as will-change
properties to support the ripple. It then dynamically generates the correct selectors such that the surface's ::before
element functions as a background ripple, and the surface's ::after
element functions as a foreground ripple.
When a ripple is successfully initialized on an element, it dynamically adds a mdc-ripple-upgraded
class to that element. If ripple is not initialized but Sass mixins are included within our surface, the ripple will still work, but it would use a simpler, CSS-Only implementation which relies on :hover
, :active
, and :focus
.
Both mdc-ripple-bg
and mdc-ripple-fg
take an $config
map as an optional
argument, with which you can specify the following parameters:
Parameter | Description | Default |
---|---|---|
pseudo | The name of the pseudo-element you want to use to style the ripple. Using pseudo-elements to style ripples obviates the need for any extra DOM and is recommended. However, if given null it will style the element directly, rather than attaching styles to the pseudo element. | null |
radius | For bounded ripples, specifies radii of the ripple circles. Can be any valid numeric CSS unit. | 100% |
theme-style | When provided, will use a style specified by mdc-theme to provide colors to the ripple. For example, passing (theme-style: primary) would make the ripples the color of the theme's primary color. Note that there are some current limitations here. See below | null |
base-color | The RGB color (without an alpha component) of the ripple. This will only be used if theme-style isn't specified. | black |
opacity | A unitless number from 0-1 specifying the opacity that either the base-color or the theme-style color will take on. | .06 |
First import the ripple JS
import {MDCRipple, MDCRippleFoundation, util} from '@material/ripple';
const {MDCRipple, MDCRippleFoundation, util} = require('@material/ripple');
require('path/to/@material/ripple', function(mdcRipple) {
const MDCRipple = mdcRipple.MDCRipple;
const MDCRippleFoundation = mdcRipple.MDCRippleFoundation;
const util = mdcRipple.util;
});
const MDCRipple = mdc.ripple.MDCRipple;
const MDCRippleFoundation = mdc.ripple.MDCRippleFoundation;
const util = mdc.ripple.util;
Then, simply initialize the ripple with the correct DOM element.
const surface = document.querySelector('.surface');
const ripple = new MDCRipple(surface);
You can also use attachTo()
as an alias if you don't care about retaining a reference to the
ripple.
MDCRipple.attachTo(document.querySelector('.surface'));
The component allows for programmatic activation / deactivation of the ripple, for interdependent interaction between components. This is used for making form field labels trigger the ripples in their corresponding input elements, for example.
Triggers an activation of the ripple (the first stage, which happens when the ripple surface is engaged via interaction,
such as a mousedown
or a pointerdown
event). It expands from the center.
Triggers a deactivation of the ripple (the second stage, which happens when the ripple surface is engaged via
interaction, such as a mouseup
or a pointerup
event). It expands from the center.
Recomputes all dimensions and positions for the ripple element. Useful if a ripple surface's position or dimension is changed programmatically.
If you'd like to use unbounded ripples, such as those used for checkboxes and radio buttons, you can do so either imperatively in JS or declaratively using the DOM.
You can set an unbounded
property to specify whether or not the ripple is unbounded.
const ripple = new MDCRipple(root);
ripple.unbounded = true;
If directly using our foundation, you must provide this information directly anyway, so simply have
isUnbounded
return true
.
const foundation = new MDCRippleFoundation({
isUnbounded: () => true,
// ...
});
If you are using our vanilla component for the ripple (not our foundation class), you can add a data attribute to your root element indicating that you wish the ripple to be unbounded:
<div class="surface" data-mdc-ripple-is-unbounded>
<p>A surface</p>
</div>
mdc-ripple contains CSS which exports an mdc-ripple-surface
class that can turn any element into
a ripple:
<style>
.my-surface {
width: 200px;
height: 200px;
background: grey; /* Google Blue 500 :) */
border-radius: 2px;
}
</style>
<!-- ... -->
<div class="mdc-ripple-surface my-surface" tabindex="0">Ripples FTW!</div>
There are also modifier classes that can be used for styling ripple surfaces using the configured theme's primary and secondary colors
<div class="mdc-ripple-surface mdc-ripple-surface--primary my-surface" tabindex="0">
Surface with a primary-colored ripple.
</div>
<div class="mdc-ripple-surface mdc-ripple-surface--accent my-surface" tabindex="0">
Surface with a secondary-colored ripple.
</div>
Check out our demo (in the top-level demos/
directory) to see these classes in action.
The MDCRippleFoundation can be used like any other foundation component. Usually, you'll want to use it in your component along with the foundation for the actual UI element you're trying to add a ripple to. The adapter API is as follows:
Method Signature | Description |
---|---|
browserSupportsCssVars() => boolean | Whether or not the given browser supports CSS Variables. When implementing this, please take the Edge and Safari considerations into account. We provide a supportsCssVariables function within the util.js which we recommend using, as it handles this for you. |
isUnbounded() => boolean | Whether or not the ripple should be considered unbounded. |
isSurfaceActive() => boolean | Whether or not the surface the ripple is acting upon is active. We use this to detect whether or not a keyboard event has activated the surface the ripple is on. This does not need to make use of :active (which is what we do); feel free to supply your own heuristics for it. |
isSurfaceDisabled() => boolean | Whether or not the ripple is attached to a disabled component. If true, the ripple will not activate. |
addClass(className: string) => void | Adds a class to the ripple surface |
removeClass(className: string) => void | Removes a class from the ripple surface |
registerInteractionHandler(evtType: string, handler: EventListener) => void | Registers an event handler that's invoked when the ripple is interacted with using type evtType . Essentially equivalent to HTMLElement.prototype.addEventListener . |
deregisterInteractionHandler(evtType: string, handler: EventListener) => void | Unregisters an event handler that's invoked when the ripple is interacted with using type evtType . Essentially equivalent to HTMLElement.prototype.removeEventListener . |
registerResizeHandler(handler: Function) => void | Registers a handler to be called when the surface (or its viewport) resizes. Our default implementation adds the handler as a listener to the window's resize() event. |
deregisterResizeHandler(handler: Function) => void | Unregisters a handler to be called when the surface (or its viewport) resizes. Our default implementation removes the handler as a listener to the window's resize() event. |
updateCssVariable(varName: string, value: (string or null)) => void | Programmatically sets the css variable varName on the surface to the value specified. |
computeBoundingRect() => ClientRect | Returns the ClientRect for the surface. |
getWindowPageOffset() => {x: number, y: number} | Returns the page{X,Y}Offset values for the window object as x and y properties of an object (respectively). |
Because ripples are used so ubiquitously throughout our codebase, MDCRipple
has a static
createAdapter(instance)
method that can be used to instantiate an adapter object that can be used by
any MDCComponent
that needs to instantiate an MDCRippleFoundation
with custom functionality.
class MyMDCComponent extends MDCComponent {
constructor() {
super(...arguments);
this.ripple_ = new MDCRippleFoundation(Object.assign(MDCRipple.createAdapter(this), {
isSurfaceActive: () => this.isActive_
}));
this.ripple_.init();
}
// ...
}
Usually, you'll want to leverage ::before
and ::after
pseudo-elements when integrating the
ripple into MDC-Web components. Furthermore, when defining your component, you can instantiate the
ripple foundation at the top level, and share logic between those adapters.
If you find you can't use pseudo-elements to style the ripple, another strategy could be to use a sentinel element that goes inside your element and covers its surface. Doing this should get you the same effect.
<div class="my-component">
<div class="mdc-ripple-surface"></div>
<!-- your component DOM -->
</div>
Different keyboard events activate different elements. For example, the space key activate buttons, while the enter key activates links. Handling this by sniffing the key/keyCode of an event is brittle and error-prone, so instead we take the approach of using adapter.isSurfaceActive()
. The
way in which our default vanilla DOM adapter determines this is by using
element.matches(':active')
. However, this approach will not work for custom components that
the browser does not apply this pseudo-class to.
If you want your component to work properly with keyboard events, you'll have to listen for both keydown
and keyup
and set some sort of state that the adapter can use to determine whether or
not the surface is "active", e.g.
class MyComponent {
constructor(el) {
this.el = el;
this.active = false;
this.ripple_ = new MDCRippleFoundation({
// ...
isSurfaceActive: () => this.active
});
this.el.addEventListener('keydown', evt => {
if (isSpace(evt)) {
this.active = true;
}
});
this.el.addEventListener('keyup', evt => {
if (isSpace(evt)) {
this.active = false;
}
});
}
}
If you asynchronously load style resources, such as loading stylesheets dynamically via scripts
or loading fonts, then adapter.getClientRect()
may by default return incorrect dimensions when
the ripple foundation is initialized. For example, if you put a ripple on an element that uses an
icon font, and the size of the icon font isn't specified at initialization time, then if that icon
font hasn't loaded it may report the intrinsic width/height incorrectly. In order to prevent this,
you can override the default behavior of getClientRect()
to return the correct results. For
example, if you know an icon font sizes its elements to 24px
width/height, you can do the
following:
this.ripple_ = new MDCRippleFoundation({
// ...
computeBoundingRect: () => {
const {left, top} = element.getBoundingClientRect();
const dim = 24;
return {
left,
top,
width: dim,
height: dim,
right: left + dim,
bottom: top + dim
};
}
});
TL;DR ripples are disabled in Edge because of issues with its support of CSS variables in pseudo elements.
Edge introduced CSS variables in version 15. Unfortunately, there are
known issues
involving its implementation for pseudo-elements which cause ripples to behave incorrectly.
We feature-detect Edge's buggy behavior as it pertains to ::before
, and do not initialize ripples if the bug is
observed. Earlier versions of Edge (and IE) are not affected, as they do not report support for CSS variables at all,
and as such ripples are never initialized.
TL;DR ripples are disabled in Safari < 10 because of a nasty CSS variables bug.
The ripple works by updating CSS Variables which are used by pseudo-elements. This allows ripple effects to work on elements without the need to add a bunch of extra DOM to them. Unfortunately, in Safari 9.1, there is a nasty bug where updating a css variable on an element will not trigger a style recalculation on that element's pseudo-elements which make use of the css variable (try out this codepen in Chrome, and then in Safari <= 9.1 to see the issue). We feature-detect around this using alternative heuristics regarding different webkit versions: Webkit builds which have this bug fixed (e.g. the builds used in Safari 10+) support CSS 4 Hex Notation while those do not have the fix don't. We use this to reliably feature-detect whether we are working with a WebKit build that can handle our usage of CSS variables.
TL;DR theme custom variable changes will not propagate to ripples if the browser does not support CSS 4 color-mod functions.
The way that mdc-theme works is that it emits two properties: one with the hard-coded sass variable, and another for a
CSS variable that can be interpolated. The problem is that ripple backgrounds need to have an opacity, and currently there's no way to opacify a pre-existing color defined by a CSS variable.
There is an editor's draft for a color-mod
function (see link in TL;DR) that can do this:
background: color(var(--mdc-theme-primary) a(6%));
But as far as we know, no browsers yet support it. We have added a @supports
clause into our code
to make sure that it can be used as soon as browsers adopt it, but for now this means that changes
to your theme via a custom variable will not propagate to ripples. We don't see this being a gigantic issue as we envision most users configuring one theme via sass. For places where you do need this, special treatment will have to be given.
External frameworks and libraries can use the following utility methods when integrating a component.
Determine whether the current browser supports CSS variables (custom properties). This function caches its result; forceRefresh
will force recomputation, but is used mainly for testing and should not be necessary in normal use.
Determine whether the current browser supports passive event listeners, and if so, use them. This function caches its result; forceRefresh
will force recomputation, but is used mainly for testing and should not be necessary in normal use.
Choose the correct matches property to use on the current browser.
Determines X/Y coordinates of an event normalized for touch events and ripples.
FAQs
The Material Components for the web Ink Ripple effect for web element interactions
The npm package @material/ripple receives a total of 754,021 weekly downloads. As such, @material/ripple popularity was classified as popular.
We found that @material/ripple demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 15 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
Security News
NVD’s backlog surpasses 20,000 CVEs as analysis slows and NIST announces new system updates to address ongoing delays.
Security News
Research
A malicious npm package disguised as a WhatsApp client is exploiting authentication flows with a remote kill switch to exfiltrate data and destroy files.