0
# Utilities
1
2
The utilities module provides helper functions for CSS variable support detection and event coordinate normalization. These functions handle browser compatibility issues and provide consistent behavior across different input types.
3
4
## Capabilities
5
6
### CSS Variable Support Detection
7
8
#### supportsCssVariables
9
10
Detects CSS custom property support with Safari 9 compatibility handling.
11
12
```typescript { .api }
13
/**
14
* Detect CSS custom property support with Safari 9 compatibility handling
15
* Includes workarounds for WebKit CSS variable bugs and caches results for performance
16
* @param windowObj - Window object to test CSS support on
17
* @param forceRefresh - Force re-detection instead of using cached result (default: false)
18
* @returns True if CSS custom properties are fully supported
19
*/
20
function supportsCssVariables(windowObj: typeof globalThis, forceRefresh?: boolean): boolean;
21
```
22
23
**Usage Examples:**
24
25
```typescript
26
import { supportsCssVariables } from "@material/ripple/util";
27
28
// Basic usage
29
const supportsVars = supportsCssVariables(window);
30
if (supportsVars) {
31
// Use CSS variable-based ripple
32
console.log("CSS variables supported");
33
} else {
34
// Fall back to simpler CSS-only ripple
35
console.log("CSS variables not supported");
36
}
37
38
// Force fresh detection (mainly for testing)
39
const freshResult = supportsCssVariables(window, true);
40
41
// Use in adapter implementation
42
const adapter = {
43
browserSupportsCssVars: () => supportsCssVariables(window),
44
// ... other methods
45
};
46
```
47
48
**Safari 9 Compatibility:**
49
50
This function includes special handling for Safari 9, which has partial CSS variable support that doesn't work correctly with pseudo-elements. The function detects this specific case and returns `false` for Safari 9 to ensure proper fallback behavior.
51
52
```typescript
53
// The function tests both:
54
// 1. CSS.supports('--css-vars', 'yes') - basic CSS variable support
55
// 2. CSS.supports('color', '#00000000') - 8-digit hex color support
56
//
57
// Safari 10+ supports both, Safari 9 only supports the first
58
// This allows detection of Safari 9's broken CSS variable implementation
59
```
60
61
### Event Coordinate Normalization
62
63
#### getNormalizedEventCoords
64
65
Normalizes touch and mouse event coordinates relative to the target element.
66
67
```typescript { .api }
68
/**
69
* Get normalized event coordinates relative to target element
70
* Handles both touch and mouse events with proper coordinate calculation
71
* @param evt - Touch or mouse event (undefined returns {x: 0, y: 0})
72
* @param pageOffset - Current window scroll offset
73
* @param clientRect - Target element's bounding rectangle
74
* @returns Normalized coordinates relative to element
75
*/
76
function getNormalizedEventCoords(
77
evt: Event | undefined,
78
pageOffset: MDCRipplePoint,
79
clientRect: DOMRect
80
): MDCRipplePoint;
81
```
82
83
**Usage Examples:**
84
85
```typescript
86
import { getNormalizedEventCoords } from "@material/ripple/util";
87
88
// In an event handler
89
function handleActivation(evt: Event) {
90
const pageOffset = { x: window.pageXOffset, y: window.pageYOffset };
91
const clientRect = element.getBoundingClientRect();
92
93
const coords = getNormalizedEventCoords(evt, pageOffset, clientRect);
94
95
console.log(`Ripple origin: ${coords.x}, ${coords.y}`);
96
97
// Use coordinates for ripple positioning
98
}
99
100
// Touch event handling
101
element.addEventListener('touchstart', (evt: TouchEvent) => {
102
const coords = getNormalizedEventCoords(
103
evt,
104
{ x: window.pageXOffset, y: window.pageYOffset },
105
element.getBoundingClientRect()
106
);
107
// coords.x and coords.y are relative to element's top-left corner
108
});
109
110
// Mouse event handling
111
element.addEventListener('mousedown', (evt: MouseEvent) => {
112
const coords = getNormalizedEventCoords(
113
evt,
114
{ x: window.pageXOffset, y: window.pageYOffset },
115
element.getBoundingClientRect()
116
);
117
// Same interface for both touch and mouse events
118
});
119
120
// Handle undefined events (programmatic activation)
121
const defaultCoords = getNormalizedEventCoords(undefined, pageOffset, clientRect);
122
console.log(defaultCoords); // { x: 0, y: 0 }
123
```
124
125
**Coordinate Calculation:**
126
127
The function performs different calculations based on event type:
128
129
```typescript
130
// For touch events: uses changedTouches[0].pageX/pageY
131
// For mouse events: uses pageX/pageY
132
// For undefined events: returns { x: 0, y: 0 }
133
134
// Final calculation:
135
// normalizedX = event.pageX - (pageOffset.x + clientRect.left)
136
// normalizedY = event.pageY - (pageOffset.y + clientRect.top)
137
```
138
139
## Advanced Usage
140
141
### Custom CSS Variable Detection
142
143
```typescript
144
import { supportsCssVariables } from "@material/ripple/util";
145
146
class CustomRippleImplementation {
147
private cssVarsSupported: boolean;
148
149
constructor(private element: HTMLElement) {
150
this.cssVarsSupported = supportsCssVariables(window);
151
152
if (this.cssVarsSupported) {
153
this.setupAdvancedRipple();
154
} else {
155
this.setupFallbackRipple();
156
}
157
}
158
159
private setupAdvancedRipple() {
160
// Use CSS variables for dynamic ripple effects
161
this.element.style.setProperty('--ripple-color', 'rgba(0, 0, 0, 0.1)');
162
console.log('Using CSS variable-based ripple');
163
}
164
165
private setupFallbackRipple() {
166
// Use static CSS classes for ripple effects
167
this.element.classList.add('ripple-fallback');
168
console.log('Using CSS-only fallback ripple');
169
}
170
}
171
```
172
173
### Event Coordinate Processing
174
175
```typescript
176
import { getNormalizedEventCoords } from "@material/ripple/util";
177
178
class RippleAnimationController {
179
constructor(private element: HTMLElement) {
180
this.setupEventHandlers();
181
}
182
183
private setupEventHandlers() {
184
// Handle multiple event types with unified coordinate processing
185
const activationEvents = ['mousedown', 'touchstart', 'pointerdown'];
186
187
activationEvents.forEach(eventType => {
188
this.element.addEventListener(eventType, (evt) => {
189
this.handleActivation(evt);
190
});
191
});
192
}
193
194
private handleActivation(evt: Event) {
195
const pageOffset = this.getPageOffset();
196
const clientRect = this.element.getBoundingClientRect();
197
198
// Get normalized coordinates for any event type
199
const coords = getNormalizedEventCoords(evt, pageOffset, clientRect);
200
201
// Calculate ripple properties based on coordinates
202
const centerX = clientRect.width / 2;
203
const centerY = clientRect.height / 2;
204
205
const distanceFromCenter = Math.sqrt(
206
Math.pow(coords.x - centerX, 2) + Math.pow(coords.y - centerY, 2)
207
);
208
209
// Use coordinates for animation positioning
210
this.animateRipple(coords, distanceFromCenter);
211
}
212
213
private getPageOffset() {
214
return {
215
x: window.pageXOffset || document.documentElement.scrollLeft,
216
y: window.pageYOffset || document.documentElement.scrollTop
217
};
218
}
219
220
private animateRipple(origin: { x: number; y: number }, distance: number) {
221
// Custom ripple animation using calculated coordinates
222
console.log(`Animating ripple from ${origin.x}, ${origin.y} with distance ${distance}`);
223
}
224
}
225
```
226
227
### Framework Integration Utilities
228
229
```typescript
230
import { supportsCssVariables, getNormalizedEventCoords } from "@material/ripple/util";
231
232
// React hook for ripple utilities
233
function useRippleUtils() {
234
const cssVarsSupported = React.useMemo(
235
() => supportsCssVariables(window),
236
[]
237
);
238
239
const normalizeEventCoords = React.useCallback(
240
(evt: React.SyntheticEvent, element: HTMLElement) => {
241
const nativeEvent = evt.nativeEvent;
242
const pageOffset = { x: window.pageXOffset, y: window.pageYOffset };
243
const clientRect = element.getBoundingClientRect();
244
245
return getNormalizedEventCoords(nativeEvent, pageOffset, clientRect);
246
},
247
[]
248
);
249
250
return { cssVarsSupported, normalizeEventCoords };
251
}
252
253
// Vue.js composition function
254
function useRipple() {
255
const cssVarsSupported = supportsCssVariables(window);
256
257
function handleRippleEvent(evt: Event, element: HTMLElement) {
258
const coords = getNormalizedEventCoords(
259
evt,
260
{ x: window.pageXOffset, y: window.pageYOffset },
261
element.getBoundingClientRect()
262
);
263
264
return coords;
265
}
266
267
return { cssVarsSupported, handleRippleEvent };
268
}
269
```
270
271
### Testing and Development
272
273
```typescript
274
import { supportsCssVariables } from "@material/ripple/util";
275
276
// Testing CSS variable support in different environments
277
function testCssVariableSupport() {
278
// Test in current environment
279
const currentSupport = supportsCssVariables(window);
280
console.log(`Current environment supports CSS vars: ${currentSupport}`);
281
282
// Force fresh detection (useful for testing)
283
const freshSupport = supportsCssVariables(window, true);
284
console.log(`Fresh detection result: ${freshSupport}`);
285
286
// Mock window object for testing
287
const mockWindow = {
288
CSS: {
289
supports: (property: string, value: string) => {
290
if (property === '--css-vars' && value === 'yes') return true;
291
if (property === 'color' && value === '#00000000') return false; // Simulate Safari 9
292
return false;
293
}
294
}
295
};
296
297
const mockSupport = supportsCssVariables(mockWindow as any);
298
console.log(`Mock Safari 9 result: ${mockSupport}`); // Should be false
299
}
300
301
// Testing coordinate normalization
302
function testCoordinateNormalization() {
303
const mockEvent = new MouseEvent('mousedown', {
304
clientX: 100,
305
clientY: 200
306
});
307
308
// Mock page offset and client rect
309
const pageOffset = { x: 50, y: 75 };
310
const clientRect = new DOMRect(25, 30, 200, 150);
311
312
const coords = getNormalizedEventCoords(mockEvent, pageOffset, clientRect);
313
console.log(`Normalized coordinates: ${coords.x}, ${coords.y}`);
314
315
// Test with undefined event
316
const defaultCoords = getNormalizedEventCoords(undefined, pageOffset, clientRect);
317
console.log(`Default coordinates: ${defaultCoords.x}, ${defaultCoords.y}`); // 0, 0
318
}
319
```
320
321
## Browser Compatibility
322
323
### CSS Variable Support Matrix
324
325
- **Chrome 49+**: Full support
326
- **Firefox 31+**: Full support
327
- **Safari 10+**: Full support
328
- **Safari 9.1**: Partial support (detected and disabled by `supportsCssVariables`)
329
- **Edge 16+**: Full support
330
- **IE**: No support (falls back to CSS-only implementation)
331
332
### Event Handling Compatibility
333
334
- **Touch Events**: Supported in all mobile browsers and modern desktop browsers
335
- **Mouse Events**: Universal support
336
- **Coordinate Normalization**: Handles all event types consistently across browsers
337
338
The utility functions ensure consistent behavior across all supported browsers by detecting capabilities and providing appropriate fallbacks.