@@ -152,21 +152,19 @@ export class RippleRenderer implements EventListenerObject {
152152 this . _mostRecentTransientRipple = rippleRef ;
153153 }
154154
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+ }
170168
171169 return rippleRef ;
172170 }
@@ -192,15 +190,17 @@ export class RippleRenderer implements EventListenerObject {
192190 const rippleEl = rippleRef . element ;
193191 const animationConfig = { ...defaultRippleAnimationConfig , ...rippleRef . config . animation } ;
194192
193+ // This starts the fade-out transition and will fire the transition end listener that
194+ // removes the ripple element from the DOM.
195195 rippleEl . style . transitionDuration = `${ animationConfig . exitDuration } ms` ;
196196 rippleEl . style . opacity = '0' ;
197197 rippleRef . state = RippleState . FADING_OUT ;
198198
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+ }
204204 }
205205
206206 /** Fades out all currently active ripples. */
@@ -254,6 +254,40 @@ export class RippleRenderer implements EventListenerObject {
254254 }
255255 }
256256
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+
257291 /** Function being called whenever the trigger is being pressed using mouse. */
258292 private _onMousedown ( event : MouseEvent ) {
259293 // Screen readers will fire fake mouse events for space/enter. Skip launching a
@@ -308,11 +342,6 @@ export class RippleRenderer implements EventListenerObject {
308342 } ) ;
309343 }
310344
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-
316345 /** Registers event listeners for a given list of events. */
317346 private _registerEvents ( eventTypes : string [ ] ) {
318347 this . _ngZone . runOutsideAngular ( ( ) => {
0 commit comments