@@ -172,21 +172,19 @@ export class RippleRenderer {
172
172
this . _mostRecentTransientRipple = rippleRef ;
173
173
}
174
174
175
- // Wait for the ripple element to be completely faded in.
176
- // Once it's faded in, the ripple can be hidden immediately if the mouse is released.
177
- this . runTimeoutOutsideZone ( ( ) => {
178
- const isMostRecentTransientRipple = rippleRef === this . _mostRecentTransientRipple ;
179
-
180
- rippleRef . state = RippleState . VISIBLE ;
181
-
182
- // When the timer runs out while the user has kept their pointer down, we want to
183
- // keep only the persistent ripples and the latest transient ripple. We do this,
184
- // because we don't want stacked transient ripples to appear after their enter
185
- // animation has finished.
186
- if ( ! config . persistent && ( ! isMostRecentTransientRipple || ! this . _isPointerDown ) ) {
187
- rippleRef . fadeOut ( ) ;
188
- }
189
- } , duration ) ;
175
+ // Do not register the transition event listener if the fade-in and fade-out duration
176
+ // are set to zero because the event won't be fired at all.
177
+ if ( duration || animationConfig . exitDuration ) {
178
+ this . _ngZone . runOutsideAngular ( ( ) => {
179
+ ripple . addEventListener ( 'transitionend' , ( ) => this . _finishRippleTransition ( rippleRef ) ) ;
180
+ } ) ;
181
+ }
182
+
183
+ // In case there is no fade-in transition duration, we need to manually call the
184
+ // transition end listener because `transitionend` doesn't fire if there is no transition.
185
+ if ( ! duration ) {
186
+ this . _finishRippleTransition ( rippleRef ) ;
187
+ }
190
188
191
189
return rippleRef ;
192
190
}
@@ -212,15 +210,17 @@ export class RippleRenderer {
212
210
const rippleEl = rippleRef . element ;
213
211
const animationConfig = { ...defaultRippleAnimationConfig , ...rippleRef . config . animation } ;
214
212
213
+ // This starts the fade-out transition and will fire the transition end listener that
214
+ // removes the ripple element from the DOM.
215
215
rippleEl . style . transitionDuration = `${ animationConfig . exitDuration } ms` ;
216
216
rippleEl . style . opacity = '0' ;
217
217
rippleRef . state = RippleState . FADING_OUT ;
218
218
219
- // Once the ripple faded out, the ripple can be safely removed from the DOM.
220
- this . runTimeoutOutsideZone ( ( ) => {
221
- rippleRef . state = RippleState . HIDDEN ;
222
- rippleEl . parentNode ! . removeChild ( rippleEl ) ;
223
- } , animationConfig . exitDuration ) ;
219
+ // In case there is no fade- out transition duration, we need to manually call the
220
+ // transition end listener because `transitionend` doesn't fire if there is no transition.
221
+ if ( ! animationConfig . exitDuration ) {
222
+ this . _finishRippleTransition ( rippleRef ) ;
223
+ }
224
224
}
225
225
226
226
/** Fades out all currently active ripples. */
@@ -245,6 +245,39 @@ export class RippleRenderer {
245
245
this . _triggerElement = element ;
246
246
}
247
247
248
+ /** Method that will be called if the fade-in or fade-in transition completed. */
249
+ private _finishRippleTransition ( rippleRef : RippleRef ) {
250
+ if ( rippleRef . state === RippleState . FADING_IN ) {
251
+ this . _startFadeOutTransition ( rippleRef ) ;
252
+ } else if ( rippleRef . state === RippleState . FADING_OUT ) {
253
+ this . _destroyRipple ( rippleRef ) ;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Starts the fade-out transition of the given ripple if it's not persistent and the pointer
259
+ * is not held down anymore.
260
+ */
261
+ private _startFadeOutTransition ( rippleRef : RippleRef ) {
262
+ const isMostRecentTransientRipple = rippleRef === this . _mostRecentTransientRipple ;
263
+
264
+ rippleRef . state = RippleState . VISIBLE ;
265
+
266
+ // When the timer runs out while the user has kept their pointer down, we want to
267
+ // keep only the persistent ripples and the latest transient ripple. We do this,
268
+ // because we don't want stacked transient ripples to appear after their enter
269
+ // animation has finished.
270
+ if ( ! rippleRef . config . persistent && ( ! isMostRecentTransientRipple || ! this . _isPointerDown ) ) {
271
+ rippleRef . fadeOut ( ) ;
272
+ }
273
+ }
274
+
275
+ /** Destroys the given ripple by removing it from the DOM and updating its state. */
276
+ private _destroyRipple ( rippleRef : RippleRef ) {
277
+ rippleRef . state = RippleState . HIDDEN ;
278
+ rippleRef . element . parentNode ! . removeChild ( rippleRef . element ) ;
279
+ }
280
+
248
281
/** Function being called whenever the trigger is being pressed using mouse. */
249
282
private onMousedown = ( event : MouseEvent ) => {
250
283
// Screen readers will fire fake mouse events for space/enter. Skip launching a
@@ -299,11 +332,6 @@ export class RippleRenderer {
299
332
} ) ;
300
333
}
301
334
302
- /** Runs a timeout outside of the Angular zone to avoid triggering the change detection. */
303
- private runTimeoutOutsideZone ( fn : Function , delay = 0 ) {
304
- this . _ngZone . runOutsideAngular ( ( ) => setTimeout ( fn , delay ) ) ;
305
- }
306
-
307
335
/** Removes previously registered event listeners from the trigger element. */
308
336
_removeTriggerEvents ( ) {
309
337
if ( this . _triggerElement ) {
0 commit comments