Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@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.
npm install @material/ripple
A ripple can be applied to a variety of elements to represent interactive surfaces. Several MDC Web components, such as Button, FAB, Checkbox and Radio, also use ripples.
A ripple can be added to an element through either a JavaScript or CSS-only implementation. When a ripple is initialized on an element using JS, it dynamically adds a mdc-ripple-upgraded
class to that element. If ripple JS is not initialized but Sass mixins are included on the element, the ripple uses a simpler CSS-only implementation which relies on the :hover
, :focus
, and :active
pseudo-classes.
CSS Class | Description |
---|---|
mdc-ripple-surface | Adds a ripple to the element |
mdc-ripple-surface--primary | Sets the ripple color to the theme primary color |
mdc-ripple-surface--accent | Sets the ripple color to the theme secondary color |
In order to fully style the ripple effect for different states (hover/focus/pressed), the following mixins must be included:
surface
, for base stylesradius-bounded
or radius-unbounded
, to appropriately size the ripple on the surfacestates
mixins, as explained below@use "@material/ripple";
.my-surface {
@include ripple.surface;
@include ripple.radius-bounded;
@include ripple.states;
}
.my-surface {
@include ripple.surface;
@include ripple.radius-bounded;
@include ripple.states-base-color(black);
@include ripple.states-opacities((hover: .1, focus: .3, press: .4));
}
These APIs use pseudo-elements for the ripple effect: ::before
for the background, and ::after
for the foreground.
Mixin | Description |
---|---|
surface | Mandatory. Adds base styles for a ripple surface |
radius-bounded($radius) | Adds styles for the radius of the ripple effect, for bounded ripple surfaces |
radius-unbounded($radius) | Adds styles for the radius of the ripple effect, for unbounded ripple surfaces |
NOTE: It is mandatory to include either
radius-bounded
orradius-unbounded
. In both cases,$radius
is optional and defaults to100%
.
Mixin | Description |
---|---|
states($color, $has-nested-focusable-element) | Mandatory. Adds state and ripple styles in the given color |
states-activated($color, $has-nested-focusable-element) | Optional. Adds state and ripple styles for activated states in the given color |
states-selected($color, $has-nested-focusable-element) | Optional. Adds state and ripple styles for selected states in the given color |
NOTE: Each of the mixins above adds ripple styles using the indicated color, deciding opacity values based on whether the passed color is light or dark.
NOTE: The
states-activated
andstates-selected
mixins add the appropriate state styles to the root element containing&--activated
or&--selected
modifier classes respectively.
NOTE:
$has-nested-focusable-element
defaults tofalse
but should be set totrue
if the component contains a focusable element (e.g. an input) inside the root element.
When using the advanced states mixins instead of the basic states mixins, every one of the mixins below should be included at least once.
These mixins can also be used to emit activated or selected styles, by applying them within a selector for
&--activated
or &--selected
modifier classes.
Mixin | Description |
---|---|
states-base-color($color) | Mandatory. Sets up base state styles using the provided color |
states-opacities($opacity-map, $has-nested-focusable-element) | Sets the opacity of the ripple in any of the hover , focus , or press states. The opacity-map can specify one or more of these states as keys. States not specified in the map resort to default opacity values. |
NOTE:
$has-nested-focusable-element
defaults tofalse
but should be set totrue
if the component contains a focusable element (e.g. an input) inside the root element.
DEPRECATED: The individual mixins
states-hover-opacity($opacity)
,states-focus-opacity($opacity, $has-nested-focusable-element)
, andstates-press-opacity($opacity)
are deprecated in favor of the unifiedstates-opacities($opacity-map, $has-nested-focusable-element)
mixin above.
Function | Description |
---|---|
states-opacity($color, $state) | Returns the appropriate default opacity to apply to the given color in the given state (hover, focus, press, selected, or activated) |
MDCRipple
The MDCRipple
JavaScript component allows for programmatic activation / deactivation of the ripple, for interdependent interaction between
components. For example, this is used for making form field labels trigger the ripples in their corresponding input elements.
To use the MDCRipple
component, first import the MDCRipple
JS. Then, initialize the ripple with the correct DOM element.
const surface = document.querySelector('.my-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('.my-surface'));
Property | Value Type | Description |
---|---|---|
unbounded | Boolean | Whether or not the ripple is unbounded |
NOTE: Surfaces for bounded ripples should have the
overflow
property set tohidden
, while surfaces for unbounded ripples should have it set tovisible
.
Method Signature | Description |
---|---|
activate() => void | Proxies to the foundation's activate method |
deactivate() => void | Proxies to the foundation's deactivate method |
layout() => void | Proxies to the foundation's layout method |
handleFocus() => void | Handles focus event on the ripple surface |
handleBlur() => void | Handles blur event on the ripple surface |
MDCRippleAdapter
Method Signature | Description |
---|---|
browserSupportsCssVars() => boolean | Whether or not the given browser supports CSS Variables. |
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 |
isSurfaceDisabled() => boolean | Whether or not the ripple is attached to a disabled component |
addClass(className: string) => void | Adds a class to the ripple surface |
removeClass(className: string) => void | Removes a class from the ripple surface |
containsEventTarget(target: EventTarget) => boolean | Whether or not the ripple surface contains the given event target |
registerInteractionHandler(evtType: string, handler: EventListener) => void | Registers an event handler on the ripple surface |
deregisterInteractionHandler(evtType: string, handler: EventListener) => void | Unregisters an event handler on the ripple surface |
registerDocumentInteractionHandler(evtType: string, handler: EventListener) => void | Registers an event handler on the documentElement |
deregisterDocumentInteractionHandler(evtType: string, handler: EventListener) => void | Unregisters an event handler on the documentElement |
registerResizeHandler(handler: Function) => void | Registers a handler to be called when the ripple surface (or its viewport) resizes |
deregisterResizeHandler(handler: Function) => void | Unregisters a handler to be called when the ripple surface (or its viewport) resizes |
updateCssVariable(varName: string, value: (string or null)) => void | Sets the CSS property varName on the ripple 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 |
NOTE: When implementing
browserSupportsCssVars
, please take the Safari 9 considerations into account. We provide asupportsCssVariables
function within theutil.js
which we recommend using, as it handles this for you.
MDCRippleFoundation
Method Signature | Description |
---|---|
activate() => void | 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. |
deactivate() => void | 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. |
layout() => void | Recomputes all dimensions and positions for the ripple element. Useful if a ripple surface's position or dimension is changed programmatically. |
setUnbounded(unbounded: boolean) => void | Sets the ripple to be unbounded or not, based on the given boolean. |
Usually, you'll want to leverage ::before
and ::after
pseudo-elements when integrating the ripple into MDC Web components. If you can't use pseudo-elements, create a sentinel element inside your root element. The sentinel element covers the root element's surface.
<div class="my-component">
<div class="mdc-ripple-surface"></div>
<!-- your component DOM -->
</div>
You can set a ripple to be unbounded, such as those used for MDC Checkboxes and MDC Radio Buttons, either imperatively in JS or declaratively using the DOM.
Set the unbounded
property on the MDCRipple
component.
const ripple = new MDCRipple(root);
ripple.unbounded = true;
Add a data-mdc-ripple-is-unbounded
attribute to your root element.
<div class="my-surface" data-mdc-ripple-is-unbounded>
<p>A surface</p>
</div>
Usually, you'll want to use MDCRipple
along with the component for the actual UI element you're trying to add a
ripple to. MDCRipple
has a static createAdapter(instance)
method that can be used to instantiate a ripple within
any MDCComponent
that requires custom adapter functionality.
class MyMDCComponent extends MDCComponent {
constructor() {
super(...arguments);
const foundation = new MDCRippleFoundation({
...MDCRipple.createAdapter(this),
isSurfaceActive: () => this.isActive, /* Custom functionality */
});
this.ripple = new MDCRipple(this.root, foundation);
}
}
Different keyboard events activate different elements. For example, the space key activates buttons, while the enter key activates links.
MDCRipple
uses the adapter.isSurfaceActive()
method to detect whether or not a keyboard event has activated the surface the ripple is on. Our vanilla implementation of the adapter does this by checking whether the :active
pseudo-class has been applied to the ripple surface. However, this approach will not work for custom components that the browser does not apply this pseudo-class to.
To make your component work properly with keyboard events, you'll have to listen for both keydown
and keyup
events to set some state that determines whether or not the surface is "active".
class MyComponent {
constructor(element) {
this.root = element;
this.active = false;
this.root.addEventListener('keydown', evt => {
if (isSpace(evt)) {
this.active = true;
}
});
this.root.addEventListener('keyup', evt => {
if (isSpace(evt)) {
this.active = false;
}
});
const foundation = new MDCRippleFoundation(
...MDCRipple.createAdapter(this),
// ...
isSurfaceActive: () => this.active
});
this.ripple = new MDCRipple(this.root, foundation);
}
}
If you asynchronously load style resources, such as loading stylesheets dynamically or loading fonts, then adapter.getClientRect()
may return incorrect dimensions if the ripple is initialized before the stylesheet/font has loaded. In this case, 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 and height:
const foundation = new MDCRippleFoundation({
// ...
computeBoundingRect: () => {
const {left, top} = element.getBoundingClientRect();
const dim = 24;
return {
left,
top,
width: dim,
height: dim,
right: left + dim,
bottom: top + dim
};
}
});
this.ripple = new MDCRipple(this.root, foundation);
External frameworks and libraries can use the following utility methods when integrating a component.
Method Signature | Description |
---|---|
util.supportsCssVariables(windowObj, forceRefresh = false) => Boolean | Determine whether the current browser supports CSS variables (custom properties) |
util.getNormalizedEventCoords(ev, pageOffset, clientRect) => object | Determines X/Y coordinates of an event normalized for touch events and ripples |
NOTE: The function
util.supportsCssVariables
cache its results;forceRefresh
will force recomputation, but is used mainly for testing and should not be necessary in normal use.
TL;DR ripples are disabled in Safari 9 because of a bug with CSS variables.
The ripple works by updating CSS variables used by pseudo-elements. Unfortunately, in Safari 9.1, there is a bug where updating a CSS variable on an element will not trigger a style recalculation on that element's pseudo-elements (try out this codepen in Chrome, and then in Safari 9.1 to see the issue). Webkit builds which have this bug fixed (e.g. the builds used in Safari 10+) support CSS 4 Hex Notation while those without the fix don't. We feature-detect whether we are working with a WebKit build that can handle our usage of CSS variables.
TL;DR for CSS-only ripple styles to work as intended, register a
touchstart
event handler on the affected element or its ancestor.
Mobile Safari does not trigger :active
styles noticeably by default, as
documented
in the Safari Web Content Guide. This effectively suppresses the intended pressed state styles for CSS-only ripple surfaces. This behavior can be remedied by registering a touchstart
event handler on the element, or on any common ancestor of the desired elements.
See this StackOverflow answer for additional information on mobile Safari's behavior.
FAQs
The Material Components for the web Ink Ripple effect for web element interactions
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
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.