Created by Steve Schwarz / @steveaschwarz / tech.agilitynerd.com
Built-in mappings for Hammer.js touch gesture events:
Does not unify mouse and gesture events.
Install HammerJS and touch-action polyfill:
$ npm install hammerjs hammer-timejs
Add includes to app.module.ts so they'll be used/bundled:
import 'hammerjs';
import 'hammer-timejs';
Each gesture defines multiple events:
pan, panstart, panmove, panend, pancancel, panleft, panright, panup, pandown
Bind desired events in template(s) and $event for use in handler:
<div class="thing-to-move"
(panstart)="onPanStart($event)"
(panmove)="onPan($event)"> Move Me! <div>
Event handlers update component x, y properties:
x: number = 0;
y: number = 0;
startX: number = 0;
startY: number = 0;
onPanStart(event: any): void {
event.preventDefault();
this.startX = this.x;
this.startY = this.y;
}
onPan(event: any): void {
event.preventDefault();
this.x = this.startX + event.deltaX;
this.y = this.startY + event.deltaY;
}
Update element's position during gestures, use bound properties in template:
<div [style.marginLeft.px]="x" [style.marginTop.px]="y" ...>
<div>Move Me!</div>
<div>({{x}}, {{y}})</div>
</div>
import { Component } from '@angular/core';
@Component({
selector: 'demo-one',
styles: ['.demo-one {width:200px;height:200px;background-color: slateblue;color: #fff;}',
'.demo-one:hover {cursor:pointer}'],
template: `
{{title}}
({{x}}, {{y}})
`
})
export class DemoOne {
x: number = 0;
y: number = 0;
title = 'Drag Me!';
startX: number = 0;
startY: number = 0;
onPanStart(event: any): void {
this.startX = this.x;
this.startY = this.y;
}
onPan(event: any): void {
event.preventDefault();
this.x = this.startX + event.deltaX;
this.y = this.startY + event.deltaY;
}
}
Put gesture code in a directive taking two inputs and outputing a locationChange event:
import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core';
@Directive({
selector: '[dragmove]'
})
export class DragMoveDirective {
@Input() x;
@Input() y;
@Output() locationChange = new EventEmitter<any>();
startX = 0;
startY = 0;
@HostListener('panstart', ['$event']) protected onPanStart(event) {
event.preventDefault();
this.startX = this.x;
this.startY = this.y;
}
@HostListener('panmove', ['$event']) protected onPanMove(event) {
event.preventDefault();
this.x = this.startX + event.deltaX;
this.y = this.startY + event.deltaY;
this.locationChange.emit({x: this.x, y: this.y});
}
}
import { Component } from '@angular/core';
@Component({
selector: 'demo-two',
styles: ['.demo-two {width:200px;height:200px;background-color: lightgreen;color: #fff;}',
'.demo-two:hover {cursor:pointer}'],
template: `
{{title}}
({{x}}, {{y}})
`
})
export class DemoTwo {
x: number = 50;
y: number = 50;
title = 'Drag Me!';
onPan(event: any): void {
this.x = event.x;
this.y = event.y;
}
}
Use mouse/touch pad and shift key to emulate multi-finger gestures during development.
<script src="/hammerjs/touch-emulator.js"></script>
<script>TouchEmulator();></script>
Chrome Dev Tools can emulate gestures.
Mobile viewport settings can impact gestures (i.e. zoom):
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
Vertical pan events can be delayed due to scroll handling. Consider:
document.body.addEventListener('touchmove', function(event) {
event.preventDefault();
}, false);
Disable default image drag:
import { Directive, HostListener } from '@angular/core';
@Directive({
selector: '[preventDefault]'
})
export class MouseDownPreventDefaultDirective {
@HostListener('mousedown', ['$event']) protected onPMouseDown(event) {
event.preventDefault();
}
}