@@ -17,6 +17,12 @@ export const RIPPLE_FADE_IN_DURATION = 450;
1717/** Fade-out duration for the ripples in milliseconds. This can't be modified by the speedFactor. */
1818export const RIPPLE_FADE_OUT_DURATION = 400 ;
1919
20+ /**
21+ * Timeout for ignoring mouse events. Mouse events will be temporary ignored after touch
22+ * events to avoid synthetic mouse events.
23+ */
24+ const IGNORE_MOUSE_EVENTS_TIMEOUT = 800 ;
25+
2026export type RippleConfig = {
2127 color ?: string ;
2228 centered ?: boolean ;
@@ -40,15 +46,18 @@ export class RippleRenderer {
4046 /** Element which triggers the ripple elements on mouse events. */
4147 private _triggerElement : HTMLElement | null ;
4248
43- /** Whether the mouse is currently down or not. */
44- private _isMousedown : boolean = false ;
49+ /** Whether the pointer is currently down or not. */
50+ private _isPointerDown = false ;
4551
4652 /** Events to be registered on the trigger element. */
4753 private _triggerEvents = new Map < string , any > ( ) ;
4854
4955 /** Set of currently active ripple references. */
5056 private _activeRipples = new Set < RippleRef > ( ) ;
5157
58+ /** Time in milliseconds when the last touchstart event happened. */
59+ private _lastTouchStartEvent : number ;
60+
5261 /** Ripple config for all ripples created by events. */
5362 rippleConfig : RippleConfig = { } ;
5463
@@ -62,8 +71,11 @@ export class RippleRenderer {
6271
6372 // Specify events which need to be registered on the trigger.
6473 this . _triggerEvents . set ( 'mousedown' , this . onMousedown . bind ( this ) ) ;
65- this . _triggerEvents . set ( 'mouseup' , this . onMouseup . bind ( this ) ) ;
66- this . _triggerEvents . set ( 'mouseleave' , this . onMouseup . bind ( this ) ) ;
74+ this . _triggerEvents . set ( 'mouseup' , this . onPointerUp . bind ( this ) ) ;
75+ this . _triggerEvents . set ( 'mouseleave' , this . onPointerUp . bind ( this ) ) ;
76+
77+ this . _triggerEvents . set ( 'touchstart' , this . onTouchStart . bind ( this ) ) ;
78+ this . _triggerEvents . set ( 'touchend' , this . onPointerUp . bind ( this ) ) ;
6779
6880 // By default use the host element as trigger element.
6981 this . setTriggerElement ( this . _containerElement ) ;
@@ -110,7 +122,7 @@ export class RippleRenderer {
110122 ripple . style . transform = 'scale(1)' ;
111123
112124 // Exposed reference to the ripple that will be returned.
113- let rippleRef = new RippleRef ( this , ripple , config ) ;
125+ const rippleRef = new RippleRef ( this , ripple , config ) ;
114126
115127 rippleRef . state = RippleState . FADING_IN ;
116128
@@ -122,7 +134,7 @@ export class RippleRenderer {
122134 this . runTimeoutOutsideZone ( ( ) => {
123135 rippleRef . state = RippleState . VISIBLE ;
124136
125- if ( ! config . persistent && ! this . _isMousedown ) {
137+ if ( ! config . persistent && ! this . _isPointerDown ) {
126138 rippleRef . fadeOut ( ) ;
127139 }
128140 } , duration ) ;
@@ -137,7 +149,7 @@ export class RippleRenderer {
137149 return ;
138150 }
139151
140- let rippleEl = rippleRef . element ;
152+ const rippleEl = rippleRef . element ;
141153
142154 rippleEl . style . transitionDuration = `${ RIPPLE_FADE_OUT_DURATION } ms` ;
143155 rippleEl . style . opacity = '0' ;
@@ -175,21 +187,37 @@ export class RippleRenderer {
175187 this . _triggerElement = element ;
176188 }
177189
178- /** Function being called whenever the trigger is being pressed. */
190+ /** Function being called whenever the trigger is being pressed using mouse . */
179191 private onMousedown ( event : MouseEvent ) {
180- if ( ! this . rippleDisabled ) {
181- this . _isMousedown = true ;
192+ const isSyntheticEvent = this . _lastTouchStartEvent &&
193+ Date . now ( ) < this . _lastTouchStartEvent + IGNORE_MOUSE_EVENTS_TIMEOUT ;
194+
195+ if ( ! this . rippleDisabled && ! isSyntheticEvent ) {
196+ this . _isPointerDown = true ;
182197 this . fadeInRipple ( event . clientX , event . clientY , this . rippleConfig ) ;
183198 }
184199 }
185200
201+ /** Function being called whenever the trigger is being pressed using touch. */
202+ private onTouchStart ( event : TouchEvent ) {
203+ if ( ! this . rippleDisabled ) {
204+ // Some browsers fire mouse events after a `touchstart` event. Those synthetic mouse
205+ // events will launch a second ripple if we don't ignore mouse events for a specific
206+ // time after a touchstart event.
207+ this . _lastTouchStartEvent = Date . now ( ) ;
208+ this . _isPointerDown = true ;
209+
210+ this . fadeInRipple ( event . touches [ 0 ] . clientX , event . touches [ 0 ] . clientY , this . rippleConfig ) ;
211+ }
212+ }
213+
186214 /** Function being called whenever the trigger is being released. */
187- private onMouseup ( ) {
188- if ( ! this . _isMousedown ) {
215+ private onPointerUp ( ) {
216+ if ( ! this . _isPointerDown ) {
189217 return ;
190218 }
191219
192- this . _isMousedown = false ;
220+ this . _isPointerDown = false ;
193221
194222 // Fade-out all ripples that are completely visible and not persistent.
195223 this . _activeRipples . forEach ( ripple => {
0 commit comments