Material Design ink ripple effect component for web element interactions with JavaScript and CSS-only implementations
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
The MDCRippleFoundation handles the core ripple logic, including activation/deactivation animations, event coordination, and CSS variable management. It follows the Material Components architecture pattern of separating business logic from DOM interactions.
Core ripple logic and state management foundation.
/**
* Core ripple logic and state management
* Handles activation/deactivation animations, event coordination, and CSS variable updates
*/
class MDCRippleFoundation extends MDCFoundation<MDCRippleAdapter> {
/** CSS class constants used by the foundation */
static readonly cssClasses: {
BG_FOCUSED: 'mdc-ripple-upgraded--background-focused';
FG_ACTIVATION: 'mdc-ripple-upgraded--foreground-activation';
FG_DEACTIVATION: 'mdc-ripple-upgraded--foreground-deactivation';
ROOT: 'mdc-ripple-upgraded';
UNBOUNDED: 'mdc-ripple-upgraded--unbounded';
};
/** CSS custom property name constants */
static readonly strings: {
VAR_FG_SCALE: '--mdc-ripple-fg-scale';
VAR_FG_SIZE: '--mdc-ripple-fg-size';
VAR_FG_TRANSLATE_END: '--mdc-ripple-fg-translate-end';
VAR_FG_TRANSLATE_START: '--mdc-ripple-fg-translate-start';
VAR_LEFT: '--mdc-ripple-left';
VAR_TOP: '--mdc-ripple-top';
};
/** Timing and dimension constants */
static readonly numbers: {
DEACTIVATION_TIMEOUT_MS: 225;
FG_DEACTIVATION_MS: 150;
INITIAL_ORIGIN_SCALE: 0.6;
PADDING: 10;
TAP_DELAY_MS: 300;
};
/** Default adapter implementation for testing */
static readonly defaultAdapter: MDCRippleAdapter;
/** Trigger ripple activation with optional event context */
activate(evt?: Event): void;
/** Trigger ripple deactivation */
deactivate(): void;
/** Recompute layout properties and CSS variables */
layout(): void;
/** Set whether ripple is bounded or unbounded */
setUnbounded(unbounded: boolean): void;
/** Handle focus events by adding focus state styles */
handleFocus(): void;
/** Handle blur events by removing focus state styles */
handleBlur(): void;
}Usage Examples:
import { MDCRippleFoundation, MDCRippleAdapter } from "@material/ripple";
// Create custom adapter
const adapter: MDCRippleAdapter = {
addClass: (className) => element.classList.add(className),
removeClass: (className) => element.classList.remove(className),
browserSupportsCssVars: () => CSS.supports('--css-vars', 'yes'),
// ... implement all required methods
};
// Create foundation with custom adapter
const foundation = new MDCRippleFoundation(adapter);
// Initialize ripple system
foundation.init();
// Programmatic control
foundation.activate(); // Trigger activation animation
foundation.deactivate(); // Trigger deactivation animation
foundation.layout(); // Recompute dimensions
// Configuration
foundation.setUnbounded(true); // Make ripple unbounded
// Event handling
foundation.handleFocus(); // Add focus styles
foundation.handleBlur(); // Remove focus styles
// Cleanup
foundation.destroy();CSS class constants used throughout the ripple system.
/**
* CSS class constants used by the foundation
*/
static readonly cssClasses: {
/** Background focus state class */
BG_FOCUSED: 'mdc-ripple-upgraded--background-focused';
/** Foreground activation animation class */
FG_ACTIVATION: 'mdc-ripple-upgraded--foreground-activation';
/** Foreground deactivation animation class */
FG_DEACTIVATION: 'mdc-ripple-upgraded--foreground-deactivation';
/** Root ripple upgrade class */
ROOT: 'mdc-ripple-upgraded';
/** Unbounded ripple class */
UNBOUNDED: 'mdc-ripple-upgraded--unbounded';
};CSS custom property name constants.
/**
* CSS custom property name constants
*/
static readonly strings: {
/** Foreground scale CSS variable */
VAR_FG_SCALE: '--mdc-ripple-fg-scale';
/** Foreground size CSS variable */
VAR_FG_SIZE: '--mdc-ripple-fg-size';
/** Translation end position CSS variable */
VAR_FG_TRANSLATE_END: '--mdc-ripple-fg-translate-end';
/** Translation start position CSS variable */
VAR_FG_TRANSLATE_START: '--mdc-ripple-fg-translate-start';
/** Left position CSS variable */
VAR_LEFT: '--mdc-ripple-left';
/** Top position CSS variable */
VAR_TOP: '--mdc-ripple-top';
};Timing and dimension constants used for animations.
/**
* Timing and dimension constants
*/
static readonly numbers: {
/** Activation animation duration in milliseconds */
DEACTIVATION_TIMEOUT_MS: 225;
/** Deactivation animation duration in milliseconds */
FG_DEACTIVATION_MS: 150;
/** Initial ripple scale factor */
INITIAL_ORIGIN_SCALE: 0.6;
/** Bounded ripple padding in pixels */
PADDING: 10;
/** Touch to mouse event delay in milliseconds */
TAP_DELAY_MS: 300;
};Default adapter implementation for testing and reference.
/**
* Default adapter implementation for testing
* All methods return safe default values
*/
static readonly defaultAdapter: MDCRippleAdapter;Triggers ripple activation animation, optionally using event coordinates.
/**
* Trigger ripple activation with optional event context
* @param evt - Optional event containing position information for ripple origin
*/
activate(evt?: Event): void;Triggers ripple deactivation animation.
/**
* Trigger ripple deactivation
* Completes the ripple animation cycle
*/
deactivate(): void;Recomputes all layout properties and updates CSS variables.
/**
* Recompute layout properties and CSS variables
* Call this when element dimensions change
*/
layout(): void;Configures whether the ripple is bounded by element dimensions.
/**
* Set whether ripple is bounded or unbounded
* @param unbounded - True for unbounded ripples (extend beyond element)
*/
setUnbounded(unbounded: boolean): void;Handles focus events by applying focus state styling.
/**
* Handle focus events by adding focus state styles
* Adds background focus highlighting
*/
handleFocus(): void;Handles blur events by removing focus state styling.
/**
* Handle blur events by removing focus state styles
* Removes background focus highlighting
*/
handleBlur(): void;import { MDCRippleFoundation, MDCRipple } from "@material/ripple";
class CustomRippleComponent {
constructor(element) {
this.root = element;
this.unbounded = false;
this.disabled = false;
this.isPressed = false;
// Create foundation with custom behavior
const foundation = new MDCRippleFoundation({
...MDCRipple.createAdapter(this),
// Override for custom active state detection
isSurfaceActive: () => this.isPressed,
// Custom CSS variable updates
updateCssVariable: (varName, value) => {
this.root.style.setProperty(varName, value);
console.log(`Updated ${varName} to ${value}`);
}
});
this.foundation = foundation;
this.foundation.init();
}
// Custom press handling
onPress() {
this.isPressed = true;
this.foundation.activate();
}
onRelease() {
this.isPressed = false;
this.foundation.deactivate();
}
}import { MDCRippleFoundation } from "@material/ripple";
// Foundation automatically handles standard activation events:
// - touchstart, pointerdown, mousedown, keydown
// - touchend, pointerup, mouseup, contextmenu, keyup
const foundation = new MDCRippleFoundation(adapter);
foundation.init(); // Registers event handlers automatically
// The foundation will:
// 1. Listen for activation events (touchstart, mousedown, etc.)
// 2. Calculate ripple origin from event coordinates
// 3. Trigger activation animation
// 4. Listen for deactivation events (touchend, mouseup, etc.)
// 5. Trigger deactivation animationimport { MDCRippleFoundation } from "@material/ripple";
// The foundation manages these CSS variables automatically:
const { strings } = MDCRippleFoundation;
// Size and positioning
console.log(strings.VAR_FG_SIZE); // '--mdc-ripple-fg-size'
console.log(strings.VAR_LEFT); // '--mdc-ripple-left'
console.log(strings.VAR_TOP); // '--mdc-ripple-top'
// Animation properties
console.log(strings.VAR_FG_SCALE); // '--mdc-ripple-fg-scale'
console.log(strings.VAR_FG_TRANSLATE_START); // '--mdc-ripple-fg-translate-start'
console.log(strings.VAR_FG_TRANSLATE_END); // '--mdc-ripple-fg-translate-end'
// Variables are updated automatically during:
// - layout() calls
// - activate() calls
// - Animation state changes// The foundation tracks several internal states:
// - isActivated: Whether ripple is currently active
// - wasActivatedByPointer: Whether activation came from touch/mouse
// - isProgrammatic: Whether activation was called programmatically
// - hasDeactivationUXRun: Whether deactivation animation has started
// These states ensure:
// - Proper animation sequencing
// - Avoiding duplicate activations
// - Coordinating with nested interactive elements
// - Managing focus and keyboard interactions