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