Skip to content

Commit 8fdc2cf

Browse files
crisbetojelbourn
authored andcommitted
feat(ripple): support multi-touch (#12643)
Currently when using multi-touch on a ripple trigger, the ripple will keep firing from the first touch position (e.g. putting one finger down and then tapping with another). These changes will fire off a ripple for each individual touch.
1 parent 3b70d20 commit 8fdc2cf

File tree

4 files changed

+43
-4
lines changed

4 files changed

+43
-4
lines changed

src/cdk/testing/event-objects.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export function createTouchEvent(type: string, pageX = 0, pageY = 0) {
4242
// the touch details.
4343
Object.defineProperties(event, {
4444
touches: {value: [touchDetails]},
45-
targetTouches: {value: [touchDetails]}
45+
targetTouches: {value: [touchDetails]},
46+
changedTouches: {value: [touchDetails]}
4647
});
4748

4849
return event;

src/lib/core/ripple/ripple-renderer.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,13 @@ export class RippleRenderer {
264264
this._lastTouchStartEvent = Date.now();
265265
this._isPointerDown = true;
266266

267-
this.fadeInRipple(
268-
event.touches[0].clientX, event.touches[0].clientY, this._target.rippleConfig);
267+
// Use `changedTouches` so we skip any touches where the user put
268+
// their finger down, but used another finger to tap the element again.
269+
const touches = event.changedTouches;
270+
271+
for (let i = 0; i < touches.length; i++) {
272+
this.fadeInRipple(touches[i].clientX, touches[i].clientY, this._target.rippleConfig);
273+
}
269274
}
270275
}
271276

src/lib/core/ripple/ripple.spec.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import {TestBed, ComponentFixture, fakeAsync, tick, inject} from '@angular/core/testing';
22
import {Component, ViewChild} from '@angular/core';
33
import {Platform} from '@angular/cdk/platform';
4-
import {dispatchMouseEvent, dispatchTouchEvent} from '@angular/cdk/testing';
4+
import {
5+
dispatchEvent,
6+
createTouchEvent,
7+
dispatchMouseEvent,
8+
dispatchTouchEvent,
9+
} from '@angular/cdk/testing';
510
import {defaultRippleAnimationConfig, RippleAnimationConfig} from './ripple-renderer';
611
import {
712
MatRipple, MatRippleModule, MAT_RIPPLE_GLOBAL_OPTIONS, RippleState, RippleGlobalOptions
@@ -121,6 +126,32 @@ describe('MatRipple', () => {
121126
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
122127
}));
123128

129+
it('should launch multiple ripples for multi-touch', fakeAsync(() => {
130+
const touchEvent = createTouchEvent('touchstart');
131+
132+
Object.defineProperties(touchEvent, {
133+
changedTouches: {
134+
value: [
135+
{pageX: 0, pageY: 0},
136+
{pageX: 10, pageY: 10},
137+
{pageX: 20, pageY: 20}
138+
]
139+
}
140+
});
141+
142+
dispatchEvent(rippleTarget, touchEvent);
143+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(3);
144+
145+
tick(enterDuration);
146+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(3);
147+
148+
dispatchTouchEvent(rippleTarget, 'touchend');
149+
150+
tick(exitDuration);
151+
152+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
153+
}));
154+
124155
it('should ignore synthetic mouse events after touchstart', () => fakeAsync(() => {
125156
dispatchTouchEvent(rippleTarget, 'touchstart');
126157
dispatchTouchEvent(rippleTarget, 'mousedown');

src/material-examples/ripple-overview/ripple-overview-example.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
width: 300px;
66
height: 300px;
77
line-height: 300px;
8+
-webkit-user-drag: none;
9+
-webkit-tap-highlight-color: transparent;
810
}
911

1012
/** Styles to make the demo look better. */

0 commit comments

Comments
 (0)