@@ -150,21 +150,19 @@ export class RippleRenderer implements EventListenerObject {
150
150
this . _mostRecentTransientRipple = rippleRef ;
151
151
}
152
152
153
- // Wait for the ripple element to be completely faded in.
154
- // Once it's faded in, the ripple can be hidden immediately if the mouse is released.
155
- this . _runTimeoutOutsideZone ( ( ) => {
156
- const isMostRecentTransientRipple = rippleRef === this . _mostRecentTransientRipple ;
157
-
158
- rippleRef . state = RippleState . VISIBLE ;
159
-
160
- // When the timer runs out while the user has kept their pointer down, we want to
161
- // keep only the persistent ripples and the latest transient ripple. We do this,
162
- // because we don't want stacked transient ripples to appear after their enter
163
- // animation has finished.
164
- if ( ! config . persistent && ( ! isMostRecentTransientRipple || ! this . _isPointerDown ) ) {
165
- rippleRef . fadeOut ( ) ;
166
- }
167
- } , duration ) ;
153
+ // Do not register the `transition` event listener if fade-in and fade-out duration
154
+ // are set to zero. The events won't fire anyway and we can save resources here.
155
+ if ( duration || animationConfig . exitDuration ) {
156
+ this . _ngZone . runOutsideAngular ( ( ) => {
157
+ ripple . addEventListener ( 'transitionend' , ( ) => this . _finishRippleTransition ( rippleRef ) ) ;
158
+ } ) ;
159
+ }
160
+
161
+ // In case there is no fade-in transition duration, we need to manually call the transition
162
+ // end listener because `transitionend` doesn't fire if there is no transition.
163
+ if ( ! duration ) {
164
+ this . _finishRippleTransition ( rippleRef ) ;
165
+ }
168
166
169
167
return rippleRef ;
170
168
}
@@ -190,15 +188,17 @@ export class RippleRenderer implements EventListenerObject {
190
188
const rippleEl = rippleRef . element ;
191
189
const animationConfig = { ...defaultRippleAnimationConfig , ...rippleRef . config . animation } ;
192
190
191
+ // This starts the fade-out transition and will fire the transition end listener that
192
+ // removes the ripple element from the DOM.
193
193
rippleEl . style . transitionDuration = `${ animationConfig . exitDuration } ms` ;
194
194
rippleEl . style . opacity = '0' ;
195
195
rippleRef . state = RippleState . FADING_OUT ;
196
196
197
- // Once the ripple faded out, the ripple can be safely removed from the DOM.
198
- this . _runTimeoutOutsideZone ( ( ) => {
199
- rippleRef . state = RippleState . HIDDEN ;
200
- rippleEl . parentNode ! . removeChild ( rippleEl ) ;
201
- } , animationConfig . exitDuration ) ;
197
+ // In case there is no fade- out transition duration, we need to manually call the
198
+ // transition end listener because `transitionend` doesn't fire if there is no transition.
199
+ if ( ! animationConfig . exitDuration ) {
200
+ this . _finishRippleTransition ( rippleRef ) ;
201
+ }
202
202
}
203
203
204
204
/** Fades out all currently active ripples. */
@@ -243,6 +243,40 @@ export class RippleRenderer implements EventListenerObject {
243
243
}
244
244
}
245
245
246
+ /** Method that will be called if the fade-in or fade-in transition completed. */
247
+ private _finishRippleTransition ( rippleRef : RippleRef ) {
248
+ if ( rippleRef . state === RippleState . FADING_IN ) {
249
+ this . _startFadeOutTransition ( rippleRef ) ;
250
+ } else if ( rippleRef . state === RippleState . FADING_OUT ) {
251
+ this . _destroyRipple ( rippleRef ) ;
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Starts the fade-out transition of the given ripple if it's not persistent and the pointer
257
+ * is not held down anymore.
258
+ */
259
+ private _startFadeOutTransition ( rippleRef : RippleRef ) {
260
+ const isMostRecentTransientRipple = rippleRef === this . _mostRecentTransientRipple ;
261
+ const { persistent} = rippleRef . config ;
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 ( ! 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
+
246
280
/** Function being called whenever the trigger is being pressed using mouse. */
247
281
private _onMousedown ( event : MouseEvent ) {
248
282
// Screen readers will fire fake mouse events for space/enter. Skip launching a
@@ -297,11 +331,6 @@ export class RippleRenderer implements EventListenerObject {
297
331
} ) ;
298
332
}
299
333
300
- /** Runs a timeout outside of the Angular zone to avoid triggering the change detection. */
301
- private _runTimeoutOutsideZone ( fn : Function , delay = 0 ) {
302
- this . _ngZone . runOutsideAngular ( ( ) => setTimeout ( fn , delay ) ) ;
303
- }
304
-
305
334
/** Registers event listeners for a given list of events. */
306
335
private _registerEvents ( eventTypes : string [ ] ) {
307
336
this . _ngZone . runOutsideAngular ( ( ) => {
0 commit comments