Skip to content

Commit 6ad8f55

Browse files
devversiontinayuangao
authored andcommitted
perf(ripple): do not register events if ripples are disabled initially (#8882)
1 parent 115f95c commit 6ad8f55

File tree

4 files changed

+131
-101
lines changed

4 files changed

+131
-101
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: 41 additions & 29 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
@@ -60,13 +72,11 @@ export class RippleRenderer {
6072
/** Options that apply to all the event listeners that are bound by the renderer. */
6173
private _eventOptions = supportsPassiveEventListeners() ? ({passive: true} as any) : false;
6274

63-
/** Ripple config for all ripples created by events. */
64-
rippleConfig: RippleConfig = {};
65-
66-
/** Whether mouse ripples should be created or not. */
67-
rippleDisabled: boolean = false;
75+
constructor(private _target: RippleTarget,
76+
private _ngZone: NgZone,
77+
elementRef: ElementRef,
78+
platform: Platform) {
6879

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

7989
this._triggerEvents.set('touchstart', this.onTouchStart);
8090
this._triggerEvents.set('touchend', this.onPointerUp);
81-
82-
// By default use the host element as trigger element.
83-
this.setTriggerElement(this._containerElement);
8491
}
8592
}
8693

@@ -170,22 +177,19 @@ export class RippleRenderer {
170177
this._activeRipples.forEach(ripple => ripple.fadeOut());
171178
}
172179

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-
});
180+
/** Sets up the trigger event listeners */
181+
setupTriggerEvents(element: HTMLElement) {
182+
if (!element || element === this._triggerElement) {
183+
return;
180184
}
181185

182-
if (element) {
183-
// If the element is not null, register all event listeners on the trigger element.
184-
this._ngZone.runOutsideAngular(() => {
185-
this._triggerEvents.forEach((fn, type) =>
186-
element.addEventListener(type, fn, this._eventOptions));
187-
});
188-
}
186+
// Remove all previously registered event listeners from the trigger element.
187+
this._removeTriggerEvents();
188+
189+
this._ngZone.runOutsideAngular(() => {
190+
this._triggerEvents.forEach((fn, type) =>
191+
element.addEventListener(type, fn, this._eventOptions));
192+
});
189193

190194
this._triggerElement = element;
191195
}
@@ -195,22 +199,23 @@ export class RippleRenderer {
195199
const isSyntheticEvent = this._lastTouchStartEvent &&
196200
Date.now() < this._lastTouchStartEvent + IGNORE_MOUSE_EVENTS_TIMEOUT;
197201

198-
if (!this.rippleDisabled && !isSyntheticEvent) {
202+
if (!this._target.rippleDisabled && !isSyntheticEvent) {
199203
this._isPointerDown = true;
200-
this.fadeInRipple(event.clientX, event.clientY, this.rippleConfig);
204+
this.fadeInRipple(event.clientX, event.clientY, this._target.rippleConfig);
201205
}
202206
}
203207

204208
/** Function being called whenever the trigger is being pressed using touch. */
205209
private onTouchStart = (event: TouchEvent) => {
206-
if (!this.rippleDisabled) {
210+
if (!this._target.rippleDisabled) {
207211
// Some browsers fire mouse events after a `touchstart` event. Those synthetic mouse
208212
// events will launch a second ripple if we don't ignore mouse events for a specific
209213
// time after a touchstart event.
210214
this._lastTouchStartEvent = Date.now();
211215
this._isPointerDown = true;
212216

213-
this.fadeInRipple(event.touches[0].clientX, event.touches[0].clientY, this.rippleConfig);
217+
this.fadeInRipple(
218+
event.touches[0].clientX, event.touches[0].clientY, this._target.rippleConfig);
214219
}
215220
}
216221

@@ -235,10 +240,17 @@ export class RippleRenderer {
235240
this._ngZone.runOutsideAngular(() => setTimeout(fn, delay));
236241
}
237242

243+
/** Removes previously registered event listeners from the trigger element. */
244+
_removeTriggerEvents() {
245+
if (this._triggerElement) {
246+
this._triggerEvents.forEach((fn, type) => {
247+
this._triggerElement!.removeEventListener(type, fn, this._eventOptions);
248+
});
249+
}
250+
}
238251
}
239252

240253
/** Enforces a style recalculation of a DOM element by computing its styles. */
241-
// TODO(devversion): Move into global utility function.
242254
function enforceStyleRecalculation(element: HTMLElement) {
243255
// Enforce a style recalculation by calling `getComputedStyle` and accessing any property.
244256
// 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)