Skip to content

Commit f590646

Browse files
devversionjelbourn
authored andcommitted
perf(ripple): do not register events if ripples are disabled initially (#8882)
1 parent 70c75ea commit f590646

File tree

4 files changed

+130
-95
lines changed

4 files changed

+130
-95
lines changed

src/lib/core/ripple/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import {PlatformModule} from '@angular/cdk/platform';
1111
import {MatCommonModule} from '../common-behaviors/common-module';
1212
import {MatRipple} from './ripple';
1313

14-
export {MatRipple, RippleGlobalOptions, MAT_RIPPLE_GLOBAL_OPTIONS} from './ripple';
15-
export {RippleRef, RippleState} from './ripple-ref';
16-
export {RippleConfig, RIPPLE_FADE_IN_DURATION, RIPPLE_FADE_OUT_DURATION} from './ripple-renderer';
14+
export * from './ripple';
15+
export * from './ripple-ref';
16+
export * from './ripple-renderer';
1717

1818
@NgModule({
1919
imports: [MatCommonModule, PlatformModule],

src/lib/core/ripple/ripple-renderer.ts

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {ElementRef, NgZone} from '@angular/core';
99
import {Platform, supportsPassiveEventListeners} from '@angular/cdk/platform';
1010
import {RippleRef, RippleState} from './ripple-ref';
1111

12-
1312
/** Fade-in duration for the ripples. Can be modified with the speedFactor option. */
1413
export const RIPPLE_FADE_IN_DURATION = 450;
1514

@@ -30,6 +29,19 @@ export type RippleConfig = {
3029
persistent?: boolean;
3130
};
3231

32+
/**
33+
* Interface that describes the target for launching ripples.
34+
* It defines the ripple configuration and disabled state for interaction ripples.
35+
* @docs-private
36+
*/
37+
export interface RippleTarget {
38+
/** Configuration for ripples that are launched on pointer down. */
39+
rippleConfig: RippleConfig;
40+
41+
/** Whether ripples on pointer down should be disabled. */
42+
rippleDisabled: boolean;
43+
}
44+
3345
/**
3446
* Helper service that performs DOM manipulations. Not intended to be used outside this module.
3547
* The constructor takes a reference to the ripple directive's host element and a map of DOM
@@ -63,10 +75,14 @@ export class RippleRenderer {
6375
/** Ripple config for all ripples created by events. */
6476
rippleConfig: RippleConfig = {};
6577

66-
/** Whether mouse ripples should be created or not. */
67-
rippleDisabled: boolean = false;
78+
/** Options that apply to all the event listeners that are bound by the renderer. */
79+
private _eventOptions = supportsPassiveEventListeners() ? ({passive: true} as any) : false;
80+
81+
constructor(private _target: RippleTarget,
82+
private _ngZone: NgZone,
83+
elementRef: ElementRef,
84+
platform: Platform) {
6885

69-
constructor(elementRef: ElementRef, private _ngZone: NgZone, platform: Platform) {
7086
// Only do anything if we're on the browser.
7187
if (platform.isBrowser) {
7288
this._containerElement = elementRef.nativeElement;
@@ -78,9 +94,6 @@ export class RippleRenderer {
7894

7995
this._triggerEvents.set('touchstart', this.onTouchStart);
8096
this._triggerEvents.set('touchend', this.onPointerUp);
81-
82-
// By default use the host element as trigger element.
83-
this.setTriggerElement(this._containerElement);
8497
}
8598
}
8699

@@ -170,22 +183,18 @@ export class RippleRenderer {
170183
this._activeRipples.forEach(ripple => ripple.fadeOut());
171184
}
172185

173-
/** Sets the trigger element and registers the mouse events. */
174-
setTriggerElement(element: HTMLElement | null) {
175-
// Remove all previously register event listeners from the trigger element.
176-
if (this._triggerElement) {
177-
this._triggerEvents.forEach((fn, type) => {
178-
this._triggerElement!.removeEventListener(type, fn, this._eventOptions);
179-
});
186+
/** Sets up the trigger event listeners */
187+
setupTriggerEvents(element: HTMLElement) {
188+
if (!element || element === this._triggerElement) {
189+
return;
180190
}
181191

182-
if (element) {
183-
// If the element is not null, register all event listeners on the trigger element.
184-
this._ngZone.runOutsideAngular(() => {
192+
// Remove all previously registeredevent listeners from the trigger element.
193+
this._removeTriggerEvents(); this._ngZone.runOutsideAngular(() => {
185194
this._triggerEvents.forEach((fn, type) =>
186195
element.addEventListener(type, fn, this._eventOptions));
187196
});
188-
}
197+
189198

190199
this._triggerElement = element;
191200
}
@@ -195,22 +204,23 @@ export class RippleRenderer {
195204
const isSyntheticEvent = this._lastTouchStartEvent &&
196205
Date.now() < this._lastTouchStartEvent + IGNORE_MOUSE_EVENTS_TIMEOUT;
197206

198-
if (!this.rippleDisabled && !isSyntheticEvent) {
207+
if (!this._target.rippleDisabled && !isSyntheticEvent) {
199208
this._isPointerDown = true;
200-
this.fadeInRipple(event.clientX, event.clientY, this.rippleConfig);
209+
this.fadeInRipple(event.clientX, event.clientY, this._target.rippleConfig);
201210
}
202211
}
203212

204213
/** Function being called whenever the trigger is being pressed using touch. */
205214
private onTouchStart = (event: TouchEvent) => {
206-
if (!this.rippleDisabled) {
215+
if (!this._target.rippleDisabled) {
207216
// Some browsers fire mouse events after a `touchstart` event. Those synthetic mouse
208217
// events will launch a second ripple if we don't ignore mouse events for a specific
209218
// time after a touchstart event.
210219
this._lastTouchStartEvent = Date.now();
211220
this._isPointerDown = true;
212221

213-
this.fadeInRipple(event.touches[0].clientX, event.touches[0].clientY, this.rippleConfig);
222+
this.fadeInRipple(
223+
event.touches[0].clientX, event.touches[0].clientY, this._target.rippleConfig);
214224
}
215225
}
216226

@@ -235,10 +245,17 @@ export class RippleRenderer {
235245
this._ngZone.runOutsideAngular(() => setTimeout(fn, delay));
236246
}
237247

248+
/** Removes previously registered event listeners from the trigger element. */
249+
_removeTriggerEvents() {
250+
if (this._triggerElement) {
251+
this._triggerEvents.forEach((fn, type) => {
252+
this._triggerElement!.removeEventListener(type, fn, this._eventOptions);
253+
});
254+
}
255+
}
238256
}
239257

240258
/** Enforces a style recalculation of a DOM element by computing its styles. */
241-
// TODO(devversion): Move into global utility function.
242259
function enforceStyleRecalculation(element: HTMLElement) {
243260
// Enforce a style recalculation by calling `getComputedStyle` and accessing any property.
244261
// Calling `getPropertyValue` is important to let optimizers know that this is not a noop.

src/lib/core/ripple/ripple.ts

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,20 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {Platform} from '@angular/cdk/platform';
910
import {
1011
Directive,
1112
ElementRef,
12-
Input,
1313
Inject,
14+
InjectionToken,
15+
Input,
1416
NgZone,
15-
OnChanges,
16-
SimpleChanges,
1717
OnDestroy,
18-
InjectionToken,
18+
OnInit,
1919
Optional,
2020
} from '@angular/core';
21-
import {Platform} from '@angular/cdk/platform';
22-
import {RippleConfig, RippleRenderer} from './ripple-renderer';
2321
import {RippleRef} from './ripple-ref';
22+
import {RippleConfig, RippleRenderer, RippleTarget} from './ripple-renderer';
2423

2524
/** Configurable options for `matRipple`. */
2625
export interface RippleGlobalOptions {
@@ -50,28 +49,20 @@ export const MAT_RIPPLE_GLOBAL_OPTIONS =
5049
'[class.mat-ripple-unbounded]': 'unbounded'
5150
}
5251
})
53-
export class MatRipple implements OnChanges, OnDestroy {
52+
export class MatRipple implements OnInit, OnDestroy, RippleTarget {
5453

55-
/**
56-
* The element that triggers the ripple when click events are received. Defaults to the
57-
* directive's host element.
58-
*/
59-
// Prevent TS metadata emit from referencing HTMLElement in ripple.js
60-
// Otherwise running this code in a Node environment (e.g Universal) will not work.
61-
@Input('matRippleTrigger') trigger: HTMLElement|HTMLElement;
54+
/** Custom color for all ripples. */
55+
@Input('matRippleColor') color: string;
56+
57+
/** Whether the ripples should be visible outside the component's bounds. */
58+
@Input('matRippleUnbounded') unbounded: boolean;
6259

6360
/**
6461
* Whether the ripple always originates from the center of the host element's bounds, rather
6562
* than originating from the location of the click event.
6663
*/
6764
@Input('matRippleCentered') centered: boolean;
6865

69-
/**
70-
* Whether click events will not trigger the ripple. Ripples can be still launched manually
71-
* by using the `launch()` method.
72-
*/
73-
@Input('matRippleDisabled') disabled: boolean;
74-
7566
/**
7667
* If set, the radius in pixels of foreground ripples when fully expanded. If unset, the radius
7768
* will be the distance from the center of the ripple to the furthest corner of the host element's
@@ -86,45 +77,59 @@ export class MatRipple implements OnChanges, OnDestroy {
8677
*/
8778
@Input('matRippleSpeedFactor') speedFactor: number = 1;
8879

89-
/** Custom color for ripples. */
90-
@Input('matRippleColor') color: string;
80+
/**
81+
* Whether click events will not trigger the ripple. Ripples can be still launched manually
82+
* by using the `launch()` method.
83+
*/
84+
@Input('matRippleDisabled')
85+
get disabled() { return this._disabled; }
86+
set disabled(value: boolean) {
87+
this._disabled = value;
88+
this._setupTriggerEventsIfEnabled();
89+
}
90+
private _disabled: boolean = false;
9191

92-
/** Whether foreground ripples should be visible outside the component's bounds. */
93-
@Input('matRippleUnbounded') unbounded: boolean;
92+
/**
93+
* The element that triggers the ripple when click events are received.
94+
* Defaults to the directive's host element.
95+
*/
96+
@Input('matRippleTrigger')
97+
get trigger() { return this._trigger || this._elementRef.nativeElement; }
98+
set trigger(trigger: HTMLElement) {
99+
this._trigger = trigger;
100+
this._setupTriggerEventsIfEnabled();
101+
}
102+
private _trigger: HTMLElement;
94103

95104
/** Renderer for the ripple DOM manipulations. */
96105
private _rippleRenderer: RippleRenderer;
97106

98107
/** Options that are set globally for all ripples. */
99108
private _globalOptions: RippleGlobalOptions;
100109

101-
constructor(
102-
elementRef: ElementRef,
103-
ngZone: NgZone,
104-
platform: Platform,
105-
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions
106-
) {
107-
this._rippleRenderer = new RippleRenderer(elementRef, ngZone, platform);
108-
this._globalOptions = globalOptions ? globalOptions : {};
110+
/** Whether ripple directive is initialized and the input bindings are set. */
111+
private _isInitialized: boolean = false;
109112

110-
this._updateRippleRenderer();
111-
}
113+
constructor(private _elementRef: ElementRef,
114+
ngZone: NgZone,
115+
platform: Platform,
116+
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions) {
112117

113-
ngOnChanges(changes: SimpleChanges) {
114-
if (changes['trigger'] && this.trigger) {
115-
this._rippleRenderer.setTriggerElement(this.trigger);
116-
}
118+
this._globalOptions = globalOptions || {};
119+
this._rippleRenderer = new RippleRenderer(this, ngZone, _elementRef, platform);
120+
}
117121

118-
this._updateRippleRenderer();
122+
ngOnInit() {
123+
this._isInitialized = true;
124+
this._setupTriggerEventsIfEnabled();
119125
}
120126

121127
ngOnDestroy() {
122-
// Set the trigger element to null to cleanup all listeners.
123-
this._rippleRenderer.setTriggerElement(null);
128+
this._rippleRenderer._removeTriggerEvents();
124129
}
125130

126131
/** Launches a manual ripple at the specified position. */
127-
launch(x: number, y: number, config: RippleConfig = this.rippleConfig): RippleRef {
132+
launch(x: number, y: number, config: RippleConfig = this): RippleRef {
128133
return this._rippleRenderer.fadeInRipple(x, y, config);
129134
}
130135

@@ -143,9 +148,16 @@ export class MatRipple implements OnChanges, OnDestroy {
143148
};
144149
}
145150

146-
/** Updates the ripple renderer with the latest ripple configuration. */
147-
_updateRippleRenderer() {
148-
this._rippleRenderer.rippleDisabled = this._globalOptions.disabled || this.disabled;
149-
this._rippleRenderer.rippleConfig = this.rippleConfig;
151+
/** Whether ripples on pointer-down are disabled or not. */
152+
get rippleDisabled(): boolean {
153+
return this.disabled || !!this._globalOptions.disabled;
154+
}
155+
156+
/** Sets up the the trigger event listeners if ripples are enabled. */
157+
private _setupTriggerEventsIfEnabled() {
158+
if (!this.disabled && this._isInitialized) {
159+
this._rippleRenderer.setupTriggerEvents(this.trigger);
160+
}
150161
}
151162
}
163+

0 commit comments

Comments
 (0)