Skip to content

Commit

Permalink
perf: listen to touch events only on touch devices (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
arturovt committed Feb 2, 2021
1 parent b7af5e7 commit c85a28d
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 109 deletions.
15 changes: 15 additions & 0 deletions src/is-touch-device.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @hidden
*/
export const IS_TOUCH_DEVICE: boolean = (() => {
// In case we're in Node.js environment.
if (typeof window === 'undefined') {
return false;
} else {
return (
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0
);
}
})();
119 changes: 66 additions & 53 deletions src/resizable.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
import { Edges } from './interfaces/edges.interface';
import { BoundingRectangle } from './interfaces/bounding-rectangle.interface';
import { ResizeEvent } from './interfaces/resize-event.interface';
import { IS_TOUCH_DEVICE } from './is-touch-device';

interface PointerEventCoordinate {
clientX: number;
Expand Down Expand Up @@ -855,7 +856,7 @@ class PointerEventListeners {
this.pointerDown = new Observable(
(observer: Observer<PointerEventCoordinate>) => {
let unsubscribeMouseDown: () => void;
let unsubscribeTouchStart: () => void;
let unsubscribeTouchStart: (() => void) | undefined;

zone.runOutsideAngular(() => {
unsubscribeMouseDown = renderer.listen(
Expand All @@ -870,30 +871,34 @@ class PointerEventListeners {
}
);

unsubscribeTouchStart = renderer.listen(
'document',
'touchstart',
(event: TouchEvent) => {
observer.next({
clientX: event.touches[0].clientX,
clientY: event.touches[0].clientY,
event
});
}
);
if (IS_TOUCH_DEVICE) {
unsubscribeTouchStart = renderer.listen(
'document',
'touchstart',
(event: TouchEvent) => {
observer.next({
clientX: event.touches[0].clientX,
clientY: event.touches[0].clientY,
event
});
}
);
}
});

return () => {
unsubscribeMouseDown();
unsubscribeTouchStart();
if (IS_TOUCH_DEVICE) {
unsubscribeTouchStart!();
}
};
}
).pipe(share());

this.pointerMove = new Observable(
(observer: Observer<PointerEventCoordinate>) => {
let unsubscribeMouseMove: () => void;
let unsubscribeTouchMove: () => void;
let unsubscribeTouchMove: (() => void) | undefined;

zone.runOutsideAngular(() => {
unsubscribeMouseMove = renderer.listen(
Expand All @@ -908,31 +913,35 @@ class PointerEventListeners {
}
);

unsubscribeTouchMove = renderer.listen(
'document',
'touchmove',
(event: TouchEvent) => {
observer.next({
clientX: event.targetTouches[0].clientX,
clientY: event.targetTouches[0].clientY,
event
});
}
);
if (IS_TOUCH_DEVICE) {
unsubscribeTouchMove = renderer.listen(
'document',
'touchmove',
(event: TouchEvent) => {
observer.next({
clientX: event.targetTouches[0].clientX,
clientY: event.targetTouches[0].clientY,
event
});
}
);
}
});

return () => {
unsubscribeMouseMove();
unsubscribeTouchMove();
if (IS_TOUCH_DEVICE) {
unsubscribeTouchMove!();
}
};
}
).pipe(share());

this.pointerUp = new Observable(
(observer: Observer<PointerEventCoordinate>) => {
let unsubscribeMouseUp: () => void;
let unsubscribeTouchEnd: () => void;
let unsubscribeTouchCancel: () => void;
let unsubscribeTouchEnd: (() => void) | undefined;
let unsubscribeTouchCancel: (() => void) | undefined;

zone.runOutsideAngular(() => {
unsubscribeMouseUp = renderer.listen(
Expand All @@ -947,35 +956,39 @@ class PointerEventListeners {
}
);

unsubscribeTouchEnd = renderer.listen(
'document',
'touchend',
(event: TouchEvent) => {
observer.next({
clientX: event.changedTouches[0].clientX,
clientY: event.changedTouches[0].clientY,
event
});
}
);

unsubscribeTouchCancel = renderer.listen(
'document',
'touchcancel',
(event: TouchEvent) => {
observer.next({
clientX: event.changedTouches[0].clientX,
clientY: event.changedTouches[0].clientY,
event
});
}
);
if (IS_TOUCH_DEVICE) {
unsubscribeTouchEnd = renderer.listen(
'document',
'touchend',
(event: TouchEvent) => {
observer.next({
clientX: event.changedTouches[0].clientX,
clientY: event.changedTouches[0].clientY,
event
});
}
);

unsubscribeTouchCancel = renderer.listen(
'document',
'touchcancel',
(event: TouchEvent) => {
observer.next({
clientX: event.changedTouches[0].clientX,
clientY: event.changedTouches[0].clientY,
event
});
}
);
}
});

return () => {
unsubscribeMouseUp();
unsubscribeTouchEnd();
unsubscribeTouchCancel();
if (IS_TOUCH_DEVICE) {
unsubscribeTouchEnd!();
unsubscribeTouchCancel!();
}
};
}
).pipe(share());
Expand Down
137 changes: 81 additions & 56 deletions src/resize-handle.directive.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import {
Directive,
Input,
HostListener,
Renderer2,
ElementRef,
OnInit,
OnDestroy,
NgZone
} from '@angular/core';
import { fromEvent, merge, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ResizableDirective } from './resizable.directive';
import { Edges } from './interfaces/edges.interface';
import { IS_TOUCH_DEVICE } from './is-touch-device';

/**
* An element placed inside a `mwlResizable` directive to be used as a drag and resize handle
Expand All @@ -24,7 +27,7 @@ import { Edges } from './interfaces/edges.interface';
@Directive({
selector: '[mwlResizeHandle]'
})
export class ResizeHandleDirective implements OnDestroy {
export class ResizeHandleDirective implements OnInit, OnDestroy {
/**
* The `Edges` object that contains the edges of the parent element that dragging the handle will trigger a resize on
*/
Expand All @@ -36,87 +39,103 @@ export class ResizeHandleDirective implements OnDestroy {
[key: string]: (() => void) | undefined;
} = {};

private destroy$ = new Subject<void>();

constructor(
private renderer: Renderer2,
private element: ElementRef,
private zone: NgZone,
private resizable: ResizableDirective
) {}

ngOnInit(): void {
this.zone.runOutsideAngular(() => {
this.listenOnTheHost<MouseEvent>('mousedown').subscribe(event => {
this.onMousedown(event, event.clientX, event.clientY);
});

this.listenOnTheHost<MouseEvent>('mouseup').subscribe(event => {
this.onMouseup(event.clientX, event.clientY);
});

if (IS_TOUCH_DEVICE) {
this.listenOnTheHost<TouchEvent>('touchstart').subscribe(event => {
this.onMousedown(
event,
event.touches[0].clientX,
event.touches[0].clientY
);
});

merge(
this.listenOnTheHost<TouchEvent>('touchend'),
this.listenOnTheHost<TouchEvent>('touchcancel')
).subscribe(event => {
this.onMouseup(
event.changedTouches[0].clientX,
event.changedTouches[0].clientY
);
});
}
});
}

ngOnDestroy(): void {
this.destroy$.next();
this.unsubscribeEventListeners();
}

/**
* @hidden
*/
@HostListener('touchstart', [
'$event',
'$event.touches[0].clientX',
'$event.touches[0].clientY'
])
@HostListener('mousedown', ['$event', '$event.clientX', '$event.clientY'])
onMousedown(
event: MouseEvent | TouchEvent,
clientX: number,
clientY: number
): void {
event.preventDefault();
this.zone.runOutsideAngular(() => {
if (!this.eventListeners.touchmove) {
this.eventListeners.touchmove = this.renderer.listen(
this.element.nativeElement,
'touchmove',
(touchMoveEvent: TouchEvent) => {
this.onMousemove(
touchMoveEvent,
touchMoveEvent.targetTouches[0].clientX,
touchMoveEvent.targetTouches[0].clientY
);
}
);
}
if (!this.eventListeners.mousemove) {
this.eventListeners.mousemove = this.renderer.listen(
this.element.nativeElement,
'mousemove',
(mouseMoveEvent: MouseEvent) => {
this.onMousemove(
mouseMoveEvent,
mouseMoveEvent.clientX,
mouseMoveEvent.clientY
);
}
);
}
this.resizable.mousedown.next({
clientX,
clientY,
edges: this.resizeEdges
});
if (!this.eventListeners.touchmove) {
this.eventListeners.touchmove = this.renderer.listen(
this.element.nativeElement,
'touchmove',
(touchMoveEvent: TouchEvent) => {
this.onMousemove(
touchMoveEvent,
touchMoveEvent.targetTouches[0].clientX,
touchMoveEvent.targetTouches[0].clientY
);
}
);
}
if (!this.eventListeners.mousemove) {
this.eventListeners.mousemove = this.renderer.listen(
this.element.nativeElement,
'mousemove',
(mouseMoveEvent: MouseEvent) => {
this.onMousemove(
mouseMoveEvent,
mouseMoveEvent.clientX,
mouseMoveEvent.clientY
);
}
);
}
this.resizable.mousedown.next({
clientX,
clientY,
edges: this.resizeEdges
});
}

/**
* @hidden
*/
@HostListener('touchend', [
'$event.changedTouches[0].clientX',
'$event.changedTouches[0].clientY'
])
@HostListener('touchcancel', [
'$event.changedTouches[0].clientX',
'$event.changedTouches[0].clientY'
])
@HostListener('mouseup', ['$event.clientX', '$event.clientY'])
onMouseup(clientX: number, clientY: number): void {
this.zone.runOutsideAngular(() => {
this.unsubscribeEventListeners();
this.resizable.mouseup.next({
clientX,
clientY,
edges: this.resizeEdges
});
this.unsubscribeEventListeners();
this.resizable.mouseup.next({
clientX,
clientY,
edges: this.resizeEdges
});
}

Expand All @@ -139,4 +158,10 @@ export class ResizeHandleDirective implements OnDestroy {
delete this.eventListeners[type];
});
}

private listenOnTheHost<T extends Event>(eventName: string) {
return fromEvent<T>(this.element.nativeElement, eventName).pipe(
takeUntil(this.destroy$)
);
}
}

0 comments on commit c85a28d

Please sign in to comment.