Skip to content

Commit

Permalink
perf(ripple): do not register events if ripples are disabled
Browse files Browse the repository at this point in the history
* No longer registers trigger event listeners if the ripples are disabled initially.

Fixes angular#8854.
  • Loading branch information
devversion committed Dec 8, 2017
1 parent 426f11f commit 80965e9
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 62 deletions.
30 changes: 14 additions & 16 deletions src/lib/core/ripple/ripple-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

import {ElementRef, NgZone} from '@angular/core';
import {Platform} from '@angular/cdk/platform';
import {MatRipple} from './ripple';
import {RippleRef, RippleState} from './ripple-ref';


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

Expand Down Expand Up @@ -58,13 +58,11 @@ export class RippleRenderer {
/** Time in milliseconds when the last touchstart event happened. */
private _lastTouchStartEvent: number;

/** Ripple config for all ripples created by events. */
rippleConfig: RippleConfig = {};

/** Whether mouse ripples should be created or not. */
rippleDisabled: boolean = false;
constructor(private _ripple: MatRipple,
private _ngZone: NgZone,
elementRef: ElementRef,
platform: Platform) {

constructor(elementRef: ElementRef, private _ngZone: NgZone, platform: Platform) {
// Only do anything if we're on the browser.
if (platform.isBrowser) {
this._containerElement = elementRef.nativeElement;
Expand All @@ -76,9 +74,6 @@ export class RippleRenderer {

this._triggerEvents.set('touchstart', this.onTouchStart.bind(this));
this._triggerEvents.set('touchend', this.onPointerUp.bind(this));

// By default use the host element as trigger element.
this.setTriggerElement(this._containerElement);
}
}

Expand Down Expand Up @@ -170,6 +165,10 @@ export class RippleRenderer {

/** Sets the trigger element and registers the mouse events. */
setTriggerElement(element: HTMLElement | null) {
if (element === this._triggerElement) {
return;
}

// Remove all previously register event listeners from the trigger element.
if (this._triggerElement) {
this._triggerEvents.forEach((fn, type) => {
Expand All @@ -192,22 +191,23 @@ export class RippleRenderer {
const isSyntheticEvent = this._lastTouchStartEvent &&
Date.now() < this._lastTouchStartEvent + IGNORE_MOUSE_EVENTS_TIMEOUT;

if (!this.rippleDisabled && !isSyntheticEvent) {
if (!this._ripple.disabled && !isSyntheticEvent) {
this._isPointerDown = true;
this.fadeInRipple(event.clientX, event.clientY, this.rippleConfig);
this.fadeInRipple(event.clientX, event.clientY, this._ripple.rippleConfig);
}
}

/** Function being called whenever the trigger is being pressed using touch. */
private onTouchStart(event: TouchEvent) {
if (!this.rippleDisabled) {
if (!this._ripple.disabled) {
// Some browsers fire mouse events after a `touchstart` event. Those synthetic mouse
// events will launch a second ripple if we don't ignore mouse events for a specific
// time after a touchstart event.
this._lastTouchStartEvent = Date.now();
this._isPointerDown = true;

this.fadeInRipple(event.touches[0].clientX, event.touches[0].clientY, this.rippleConfig);
this.fadeInRipple(
event.touches[0].clientX, event.touches[0].clientY, this._ripple.rippleConfig);
}
}

Expand All @@ -231,11 +231,9 @@ export class RippleRenderer {
private runTimeoutOutsideZone(fn: Function, delay = 0) {
this._ngZone.runOutsideAngular(() => setTimeout(fn, delay));
}

}

/** Enforces a style recalculation of a DOM element by computing its styles. */
// TODO(devversion): Move into global utility function.
function enforceStyleRecalculation(element: HTMLElement) {
// Enforce a style recalculation by calling `getComputedStyle` and accessing any property.
// Calling `getPropertyValue` is important to let optimizers know that this is not a noop.
Expand Down
110 changes: 65 additions & 45 deletions src/lib/core/ripple/ripple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Platform} from '@angular/cdk/platform';
import {
Directive,
ElementRef,
Input,
Inject,
InjectionToken,
Input,
NgZone,
OnChanges,
SimpleChanges,
OnDestroy,
InjectionToken,
OnInit,
Optional,
} from '@angular/core';
import {Platform} from '@angular/cdk/platform';
import {RippleConfig, RippleRenderer} from './ripple-renderer';
import {RippleRef} from './ripple-ref';
import {RippleConfig, RippleRenderer} from './ripple-renderer';

/** Configurable options for `matRipple`. */
export interface RippleGlobalOptions {
Expand Down Expand Up @@ -50,28 +49,20 @@ export const MAT_RIPPLE_GLOBAL_OPTIONS =
'[class.mat-ripple-unbounded]': 'unbounded'
}
})
export class MatRipple implements OnChanges, OnDestroy {
export class MatRipple implements OnInit, OnDestroy {

/**
* The element that triggers the ripple when click events are received. Defaults to the
* directive's host element.
*/
// Prevent TS metadata emit from referencing HTMLElement in ripple.js
// Otherwise running this code in a Node environment (e.g Universal) will not work.
@Input('matRippleTrigger') trigger: HTMLElement|HTMLElement;
/** Custom color for all ripples. */
@Input('matRippleColor') color: string;

/** Whether the ripples should be visible outside the component's bounds. */
@Input('matRippleUnbounded') unbounded: boolean;

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

/**
* Whether click events will not trigger the ripple. Ripples can be still launched manually
* by using the `launch()` method.
*/
@Input('matRippleDisabled') disabled: boolean;

/**
* If set, the radius in pixels of foreground ripples when fully expanded. If unset, the radius
* will be the distance from the center of the ripple to the furthest corner of the host element's
Expand All @@ -86,36 +77,71 @@ export class MatRipple implements OnChanges, OnDestroy {
*/
@Input('matRippleSpeedFactor') speedFactor: number = 1;

/** Custom color for ripples. */
@Input('matRippleColor') color: string;
/**
* The element that triggers the ripple when click events are received.
* Defaults to the directive's host element.
*/
@Input('matRippleTrigger')
get trigger() { return this._trigger; }
set trigger(trigger: HTMLElement | null) {
this._trigger = trigger;

// The events for a trigger element shouldn't be registered if ripples are disabled.
// As soon as the ripples are enabled again, the events for the current trigger will be added.
if (!this.disabled && this._isInitialized) {
this._rippleRenderer.setTriggerElement(trigger);
}
}
private _trigger: HTMLElement | null;

/** Whether foreground ripples should be visible outside the component's bounds. */
@Input('matRippleUnbounded') unbounded: boolean;
/**
* Whether click events will not trigger the ripple. Ripples can be still launched manually
* by using the `launch()` method.
*/
@Input('matRippleDisabled')
get disabled() { return this._disabled || !!this._globalOptions.disabled; }
set disabled(value: boolean) {
this._disabled = value;

// The events for the trigger element haven't been added if ripples were disabled. This means
// that the events for the trigger element need to be registered if ripples are enabled again.
if (!this.disabled && this._isInitialized) {
this._rippleRenderer.setTriggerElement(this._trigger);
}
}
private _disabled: boolean = false;

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

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

constructor(
elementRef: ElementRef,
ngZone: NgZone,
platform: Platform,
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions
) {
this._rippleRenderer = new RippleRenderer(elementRef, ngZone, platform);
this._globalOptions = globalOptions ? globalOptions : {};
/** Reference to the directive's host HTMLElement. */
private _hostElement: HTMLElement;

/** Whether ripple directive is initialized and the input bindings are set. */
private _isInitialized: boolean = false;

this._updateRippleRenderer();
constructor(elementRef: ElementRef,
ngZone: NgZone,
platform: Platform,
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions) {

this._globalOptions = globalOptions || {};
this._hostElement = elementRef.nativeElement;
this._rippleRenderer = new RippleRenderer(this, ngZone, elementRef, platform);
}

ngOnChanges(changes: SimpleChanges) {
if (changes['trigger'] && this.trigger) {
this._rippleRenderer.setTriggerElement(this.trigger);
}
ngOnInit() {
this._isInitialized = true;

this._updateRippleRenderer();
// The trigger element might have been set before the `ngOnInit` lifecycle hook, but the
// events haven't been registered yet. This has been deferred to the `ngOnInit` lifecycle hook,
// because the events should be only registered if the ripples aren't disabled initially.
// Also if there is no trigger element, that has been specified explicitly through the input,
// the default trigger element will be the directive's host element.
this.trigger = this._trigger === undefined ? this._hostElement : this.trigger;
}

ngOnDestroy() {
Expand All @@ -124,7 +150,7 @@ export class MatRipple implements OnChanges, OnDestroy {
}

/** Launches a manual ripple at the specified position. */
launch(x: number, y: number, config: RippleConfig = this.rippleConfig): RippleRef {
launch(x: number, y: number, config: RippleConfig = this): RippleRef {
return this._rippleRenderer.fadeInRipple(x, y, config);
}

Expand All @@ -142,10 +168,4 @@ export class MatRipple implements OnChanges, OnDestroy {
color: this.color
};
}

/** Updates the ripple renderer with the latest ripple configuration. */
_updateRippleRenderer() {
this._rippleRenderer.rippleDisabled = this._globalOptions.disabled || this.disabled;
this._rippleRenderer.rippleConfig = this.rippleConfig;
}
}
2 changes: 1 addition & 1 deletion src/lib/tabs/tab-nav-bar/tab-nav-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ export class MatTabLink extends _MatTabLinkMixinBase
set disableRipple(value: boolean) {
this._disableRipple = value;
this._tabLinkRipple.disabled = this.disableRipple;
this._tabLinkRipple._updateRippleRenderer();
}

constructor(private _tabNavBar: MatTabNav,
Expand All @@ -229,6 +228,7 @@ export class MatTabLink extends _MatTabLinkMixinBase
// Manually create a ripple instance that uses the tab link element as trigger element.
// Notice that the lifecycle hooks for the ripple config won't be called anymore.
this._tabLinkRipple = new MatRipple(_elementRef, ngZone, platform, globalOptions);
this._tabLinkRipple.ngOnInit();

this.tabIndex = parseInt(tabIndex) || 0;
}
Expand Down

0 comments on commit 80965e9

Please sign in to comment.