@@ -154,21 +154,19 @@ export class RippleRenderer implements EventListenerObject {
154
154
this . _mostRecentTransientRipple = rippleRef ;
155
155
}
156
156
157
- // Wait for the ripple element to be completely faded in.
158
- // Once it's faded in, the ripple can be hidden immediately if the mouse is released.
159
- this . _runTimeoutOutsideZone ( ( ) => {
160
- const isMostRecentTransientRipple = rippleRef === this . _mostRecentTransientRipple ;
161
-
162
- rippleRef . state = RippleState . VISIBLE ;
163
-
164
- // When the timer runs out while the user has kept their pointer down, we want to
165
- // keep only the persistent ripples and the latest transient ripple. We do this,
166
- // because we don't want stacked transient ripples to appear after their enter
167
- // animation has finished.
168
- if ( ! config . persistent && ( ! isMostRecentTransientRipple || ! this . _isPointerDown ) ) {
169
- rippleRef . fadeOut ( ) ;
170
- }
171
- } , duration ) ;
157
+ // Do not register the `transition` event listener if fade-in and fade-out duration
158
+ // are set to zero. The events won't fire anyway and we can save resources here.
159
+ if ( duration || animationConfig . exitDuration ) {
160
+ this . _ngZone . runOutsideAngular ( ( ) => {
161
+ ripple . addEventListener ( 'transitionend' , ( ) => this . _finishRippleTransition ( rippleRef ) ) ;
162
+ } ) ;
163
+ }
164
+
165
+ // In case there is no fade-in transition duration, we need to manually call the transition
166
+ // end listener because `transitionend` doesn't fire if there is no transition.
167
+ if ( ! duration ) {
168
+ this . _finishRippleTransition ( rippleRef ) ;
169
+ }
172
170
173
171
return rippleRef ;
174
172
}
@@ -194,15 +192,17 @@ export class RippleRenderer implements EventListenerObject {
194
192
const rippleEl = rippleRef . element ;
195
193
const animationConfig = { ...defaultRippleAnimationConfig , ...rippleRef . config . animation } ;
196
194
195
+ // This starts the fade-out transition and will fire the transition end listener that
196
+ // removes the ripple element from the DOM.
197
197
rippleEl . style . transitionDuration = `${ animationConfig . exitDuration } ms` ;
198
198
rippleEl . style . opacity = '0' ;
199
199
rippleRef . state = RippleState . FADING_OUT ;
200
200
201
- // Once the ripple faded out, the ripple can be safely removed from the DOM.
202
- this . _runTimeoutOutsideZone ( ( ) => {
203
- rippleRef . state = RippleState . HIDDEN ;
204
- rippleEl . remove ( ) ;
205
- } , animationConfig . exitDuration ) ;
201
+ // In case there is no fade- out transition duration, we need to manually call the
202
+ // transition end listener because `transitionend` doesn't fire if there is no transition.
203
+ if ( ! animationConfig . exitDuration ) {
204
+ this . _finishRippleTransition ( rippleRef ) ;
205
+ }
206
206
}
207
207
208
208
/** Fades out all currently active ripples. */
@@ -256,6 +256,40 @@ export class RippleRenderer implements EventListenerObject {
256
256
}
257
257
}
258
258
259
+ /** Method that will be called if the fade-in or fade-in transition completed. */
260
+ private _finishRippleTransition ( rippleRef : RippleRef ) {
261
+ if ( rippleRef . state === RippleState . FADING_IN ) {
262
+ this . _startFadeOutTransition ( rippleRef ) ;
263
+ } else if ( rippleRef . state === RippleState . FADING_OUT ) {
264
+ this . _destroyRipple ( rippleRef ) ;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Starts the fade-out transition of the given ripple if it's not persistent and the pointer
270
+ * is not held down anymore.
271
+ */
272
+ private _startFadeOutTransition ( rippleRef : RippleRef ) {
273
+ const isMostRecentTransientRipple = rippleRef === this . _mostRecentTransientRipple ;
274
+ const { persistent} = rippleRef . config ;
275
+
276
+ rippleRef . state = RippleState . VISIBLE ;
277
+
278
+ // When the timer runs out while the user has kept their pointer down, we want to
279
+ // keep only the persistent ripples and the latest transient ripple. We do this,
280
+ // because we don't want stacked transient ripples to appear after their enter
281
+ // animation has finished.
282
+ if ( ! persistent && ( ! isMostRecentTransientRipple || ! this . _isPointerDown ) ) {
283
+ rippleRef . fadeOut ( ) ;
284
+ }
285
+ }
286
+
287
+ /** Destroys the given ripple by removing it from the DOM and updating its state. */
288
+ private _destroyRipple ( rippleRef : RippleRef ) {
289
+ rippleRef . state = RippleState . HIDDEN ;
290
+ rippleRef . element . remove ( ) ;
291
+ }
292
+
259
293
/** Function being called whenever the trigger is being pressed using mouse. */
260
294
private _onMousedown ( event : MouseEvent ) {
261
295
// Screen readers will fire fake mouse events for space/enter. Skip launching a
@@ -312,11 +346,6 @@ export class RippleRenderer implements EventListenerObject {
312
346
} ) ;
313
347
}
314
348
315
- /** Runs a timeout outside of the Angular zone to avoid triggering the change detection. */
316
- private _runTimeoutOutsideZone ( fn : Function , delay = 0 ) {
317
- this . _ngZone . runOutsideAngular ( ( ) => setTimeout ( fn , delay ) ) ;
318
- }
319
-
320
349
/** Registers event listeners for a given list of events. */
321
350
private _registerEvents ( eventTypes : string [ ] ) {
322
351
this . _ngZone . runOutsideAngular ( ( ) => {
0 commit comments