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