Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ripple): support animation duration overwrites #9253

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/demo-app/ripple/ripple-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@
</mat-form-field>
</section>
<section>
<button mat-raised-button (click)="launchRipple()">Launch Ripple</button>
<button mat-raised-button (click)="launchRipple(true)">Launch Ripple (Persistent)</button>
<button mat-raised-button (click)="fadeOutAll()">Fade Out All</button>
<button mat-raised-button (click)="launchRipple()" disableRipple>Launch Ripple</button>
<button mat-raised-button (click)="launchRipple(true)" disableRipple>Launch Ripple (Persistent)</button>
<button mat-raised-button (click)="launchRipple(true, true)" disableRipple>Launch Ripple (No Animation)</button>
<button mat-raised-button (click)="fadeOutAll()" disableRipple>Fade Out All</button>
</section>
<section>
<div class="demo-ripple-container"
Expand Down
14 changes: 11 additions & 3 deletions src/demo-app/ripple/ripple-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,18 @@ export class RippleDemo {

disableButtonRipples = false;

launchRipple(persistent = false) {
if (this.ripple) {
this.ripple.launch(0, 0, { centered: true, persistent });
launchRipple(persistent = false, disableAnimation = false) {
if (!this.ripple) {
return;
}

const rippleConfig = {
centered: true,
persistent: persistent,
animation: disableAnimation ? {enterDuration: 0, exitDuration: 0} : undefined
};

this.ripple.launch(0, 0, rippleConfig);
}

fadeOutAll() {
Expand Down
6 changes: 3 additions & 3 deletions src/lib/checkbox/checkbox.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
<div matRipple class="mat-checkbox-ripple"
[matRippleTrigger]="label"
[matRippleDisabled]="_isRippleDisabled()"
[matRippleRadius]="_rippleConfig.radius"
[matRippleSpeedFactor]="_rippleConfig.speedFactor"
[matRippleCentered]="_rippleConfig.centered">
[matRippleRadius]="25"
[matRippleCentered]="true"
[matRippleAnimation]="{enterDuration: 300}">
</div>
<div class="mat-checkbox-frame"></div>
<div class="mat-checkbox-background">
Expand Down
6 changes: 3 additions & 3 deletions src/lib/checkbox/checkbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {Component, DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
import {dispatchFakeEvent} from '@angular/cdk/testing';
import {MatCheckbox, MatCheckboxChange, MatCheckboxModule} from './index';
import {RIPPLE_FADE_IN_DURATION, RIPPLE_FADE_OUT_DURATION} from '@angular/material/core';
import {defaultRippleAnimationConfig} from '@angular/material/core';
import {MAT_CHECKBOX_CLICK_ACTION} from './checkbox-config';
import {MutationObserverFactory} from '@angular/cdk/observers';

Expand Down Expand Up @@ -390,13 +390,13 @@ describe('MatCheckbox', () => {
dispatchFakeEvent(inputElement, 'keydown');
dispatchFakeEvent(inputElement, 'focus');

tick(RIPPLE_FADE_IN_DURATION);
tick(defaultRippleAnimationConfig.enterDuration);

expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
.toBe(1, 'Expected ripple after element is focused.');

dispatchFakeEvent(checkboxInstance._inputElement.nativeElement, 'blur');
tick(RIPPLE_FADE_OUT_DURATION);
tick(defaultRippleAnimationConfig.exitDuration);

expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
.toBe(0, 'Expected no ripple after element is blurred.');
Expand Down
6 changes: 1 addition & 5 deletions src/lib/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import {
mixinDisabled,
mixinDisableRipple,
mixinTabIndex,
RippleConfig,
RippleRef,
} from '@angular/material/core';
import {MAT_CHECKBOX_CLICK_ACTION, MatCheckboxClickAction} from './checkbox-config';
Expand Down Expand Up @@ -180,9 +179,6 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
/** Reference to the ripple instance of the checkbox. */
@ViewChild(MatRipple) ripple: MatRipple;

/** Ripple configuration for the mouse ripples and focus indicators. */
_rippleConfig: RippleConfig = {centered: true, radius: 25, speedFactor: 1.5};

/**
* Called when the checkbox is blurred. Needed to properly implement ControlValueAccessor.
* @docs-private
Expand Down Expand Up @@ -341,7 +337,7 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
/** Function is called whenever the focus changes for the input element. */
private _onInputFocusChange(focusOrigin: FocusOrigin) {
if (!this._focusRipple && focusOrigin === 'keyboard') {
this._focusRipple = this.ripple.launch(0, 0, {persistent: true, ...this._rippleConfig});
this._focusRipple = this.ripple.launch(0, 0, {persistent: true});
} else if (!focusOrigin) {
this._removeFocusRipple();
this.onTouched();
Expand Down
53 changes: 35 additions & 18 deletions src/lib/core/ripple/ripple-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,27 @@ import {ElementRef, NgZone} from '@angular/core';
import {Platform, supportsPassiveEventListeners} from '@angular/cdk/platform';
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;

/** Fade-out duration for the ripples in milliseconds. This can't be modified by the speedFactor. */
export const RIPPLE_FADE_OUT_DURATION = 400;

/**
* Timeout for ignoring mouse events. Mouse events will be temporary ignored after touch
* events to avoid synthetic mouse events.
*/
const IGNORE_MOUSE_EVENTS_TIMEOUT = 800;

export type RippleConfig = {
color?: string;
centered?: boolean;
radius?: number;
speedFactor?: number;
persistent?: boolean;
animation?: RippleAnimationConfig;
/** @deprecated Use the animation property instead. */
speedFactor?: number;
};

/**
* Interface that describes the configuration for the animation of a ripple.
* There are two animation phases with different durations for the ripples.
*/
export interface RippleAnimationConfig {
/** Duration in milliseconds for the enter animation (expansion from point of contact). */
enterDuration?: number;
/** Duration in milliseconds for the exit animation (fade-out). */
exitDuration?: number;
}

/**
* Interface that describes the target for launching ripples.
* It defines the ripple configuration and disabled state for interaction ripples.
Expand All @@ -37,11 +38,25 @@ export type RippleConfig = {
export interface RippleTarget {
/** Configuration for ripples that are launched on pointer down. */
rippleConfig: RippleConfig;

/** Whether ripples on pointer down should be disabled. */
rippleDisabled: boolean;
}

/**
* Default ripple animation configuration for ripples without an explicit
* animation config specified.
*/
export const defaultRippleAnimationConfig = {
enterDuration: 450,
exitDuration: 400
};

/**
* Timeout for ignoring mouse events. Mouse events will be temporary ignored after touch
* events to avoid synthetic mouse events.
*/
const ignoreMouseEventsTimeout = 800;

/**
* Helper service that performs DOM manipulations. Not intended to be used outside this module.
* The constructor takes a reference to the ripple directive's host element and a map of DOM
Expand Down Expand Up @@ -99,16 +114,17 @@ export class RippleRenderer {
*/
fadeInRipple(x: number, y: number, config: RippleConfig = {}): RippleRef {
const containerRect = this._containerElement.getBoundingClientRect();
const animationConfig = {...defaultRippleAnimationConfig, ...config.animation};

if (config.centered) {
x = containerRect.left + containerRect.width / 2;
y = containerRect.top + containerRect.height / 2;
}

const radius = config.radius || distanceToFurthestCorner(x, y, containerRect);
const duration = RIPPLE_FADE_IN_DURATION / (config.speedFactor || 1);
const offsetX = x - containerRect.left;
const offsetY = y - containerRect.top;
const duration = animationConfig.enterDuration / (config.speedFactor || 1);

const ripple = document.createElement('div');
ripple.classList.add('mat-ripple-element');
Expand Down Expand Up @@ -159,8 +175,9 @@ export class RippleRenderer {
}

const rippleEl = rippleRef.element;
const animationConfig = {...defaultRippleAnimationConfig, ...rippleRef.config.animation};

rippleEl.style.transitionDuration = `${RIPPLE_FADE_OUT_DURATION}ms`;
rippleEl.style.transitionDuration = `${animationConfig.exitDuration}ms`;
rippleEl.style.opacity = '0';

rippleRef.state = RippleState.FADING_OUT;
Expand All @@ -169,7 +186,7 @@ export class RippleRenderer {
this.runTimeoutOutsideZone(() => {
rippleRef.state = RippleState.HIDDEN;
rippleEl.parentNode!.removeChild(rippleEl);
}, RIPPLE_FADE_OUT_DURATION);
}, animationConfig.exitDuration);
}

/** Fades out all currently active ripples. */
Expand Down Expand Up @@ -197,7 +214,7 @@ export class RippleRenderer {
/** Function being called whenever the trigger is being pressed using mouse. */
private onMousedown = (event: MouseEvent) => {
const isSyntheticEvent = this._lastTouchStartEvent &&
Date.now() < this._lastTouchStartEvent + IGNORE_MOUSE_EVENTS_TIMEOUT;
Date.now() < this._lastTouchStartEvent + ignoreMouseEventsTimeout;

if (!this._target.rippleDisabled && !isSyntheticEvent) {
this._isPointerDown = true;
Expand Down
27 changes: 25 additions & 2 deletions src/lib/core/ripple/ripple.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,11 @@ Global ripple options can be specified by setting the `MAT_RIPPLE_GLOBAL_OPTIONS
```ts
const globalRippleConfig: RippleGlobalOptions = {
disabled: true,
baseSpeedFactor: 1.5 // Ripples will animate 50% faster than before.
}
animation: {
enterDuration: 300,
exitDuration: 0
}
};

@NgModule({
providers: [
Expand All @@ -86,3 +89,23 @@ const globalRippleConfig: RippleGlobalOptions = {
```

All available global options can be seen in the `RippleGlobalOptions` interface.

### Disabling animation

The animation of ripples can be disabled by using the `animation` global option. If the
`enterDuration` and `exitDuration` is being set to `0`, ripples will just appear without any
animation.

This is specifically useful in combination with the `disabled` global option, because globally
disabling ripples won't affect the focus indicator ripples. If someone still wants to disable
those ripples for performance reasons, the duration can be set to `0`, to remove the ripple feel.

```ts
const globalRippleConfig: RippleGlobalOptions = {
disabled: true,
animation: {
enterDuration: 0,
exitDuration: 0
}
};
```
Loading