I want to create a draggable / resizable / rotatable component in Ionic2
.pan
and pinch
events are working great, but rotate
has a strange behaviour: if I touch the component with two fingers, but without doing any kind of rotation, I will still get a rotation
number around 15 to 30 deg, making the component rotate. I don't know if it is a known issue or something to do with the sensitivity of the screen. The code I am using for the component is this:
import { Component, ElementRef, Input, Renderer2 } from '@angular/core';
import { DomController, Gesture } from 'ionic-angular';
const defaultScale: number = 1;
const defaultRotation: number = 0;
@Component({
selector: 'draggable',
template: `
<ng-content></ng-content>
`
})
export class DraggableComponent {
@Input()
private position: {
x: number;
y: number;
};
@Input()
private dimensions: {
width: number;
height: number;
};
@Input()
private transform: {
scale: number;
rotation: number;
};
@Input()
protected container: any;
private gesture: Gesture;
private deltaCenter: {
x: number;
y: number;
} = null;
// when pinch + rotate, we will have very quick successive event when we release
private updating: boolean = false;
constructor(
private element: ElementRef,
private renderer: Renderer2,
private domCtrl: DomController
) {}
ngOnDestroy() {
this.gesture.destroy();
}
ngAfterViewInit() {
this.renderer.setStyle(this.element.nativeElement, 'position', 'absolute');
this.renderer.setStyle(this.element.nativeElement, 'transform-origin', 'center');
if (this.dimensions) {
if (this.dimensions.width) {
this.renderer.setStyle(this.element.nativeElement, 'width', this.dimensions.width + 'px');
}
if (this.dimensions.height) {
this.renderer.setStyle(this.element.nativeElement, 'height', this.dimensions.height + 'px');
}
}
if (!this.transform) {
this.transform = {
scale: 1,
rotation: 0
};
}
this.gesture = new Gesture(this.element.nativeElement);
this.gesture.listen();
this.gesture.on('pinch', this.handleGesture.bind(this));
this.gesture.on('rotate', this.handleGesture.bind(this));
this.gesture.on('panmove', this.handleGesture.bind(this));
this.gesture.on('pinchend panend rotateend', this.gestureEnd.bind(this));
this.updateStyles();
}
private handleGesture(event: {center: {y: number, x: number}, scale: number, rotation: number}) {
if (this.updating) {
return;
}
// even without doing any kind of rotation, using 2 fingers will set event.rotation between 15 to 30 degrees
if (!this.deltaCenter) {
this.deltaCenter = {
y: this.position.y - event.center.y,
x: this.position.x - event.center.x
};
}
this.position.y = event.center.y;
this.position.x = event.center.x;
this.updateStyles(event.scale, event.rotation);
}
private gestureEnd(event: {scale: number, rotation: number}) {
if (this.updating) {
return;
}
this.updating = true;
this.position.y += this.deltaCenter.y;
this.position.x += this.deltaCenter.x;
this.transform.scale = this.transform.scale * event.scale;
this.transform.rotation = this.transform.rotation + event.rotation;
this.deltaCenter = null;
this.updateStyles();
setTimeout(() => {
this.updating = false;
}, 100);
}
private get cntEle(): HTMLElement {
let cntEle: HTMLElement = null;
if (!this.container) {
return null;
}
else if (this.container instanceof Node) {
return this.container as HTMLElement;
}
else if (this.container.getNativeElement) {
return this.container.getNativeElement();
}
return null;
}
private get containerBoundingClientRect(): ClientRect {
if (this.cntEle) {
return this.cntEle.getBoundingClientRect();
}
else if (this.container && 'top' in this.container) {
return this.container as ClientRect;
}
// bound to whole document
return {
top: 0,
left: 0,
bottom: document.documentElement.clientHeight,
right: document.documentElement.clientWidth,
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
};
}
private get x(): number {
let x = this.position.x;
if (this.deltaCenter) {
x += this.deltaCenter.x;
}
if (x < this.containerBoundingClientRect.left) {
return this.containerBoundingClientRect.left;
}
else if (x > (this.containerBoundingClientRect.right - this.dimensions.width)) {
return this.containerBoundingClientRect.right - this.dimensions.width;
}
return x
}
private get y(): number {
let y = this.position.y;
if (this.deltaCenter) {
y += this.deltaCenter.y;
}
if (y < this.containerBoundingClientRect.top) {
return this.containerBoundingClientRect.top;
}
if (y > (this.containerBoundingClientRect.bottom - this.dimensions.height)) {
return this.containerBoundingClientRect.bottom - this.dimensions.height;
}
return y;
}
private updateStyles(scale: number = 1, rotation: number = 0) {
this.domCtrl.write(() => {
this.renderer.setStyle(this.element.nativeElement, 'top', this.y + 'px');
this.renderer.setStyle(this.element.nativeElement, 'left', this.x + 'px');
let transforms = [];
transforms.push(`scale(${this.transform.scale * scale})`);
transforms.push(`rotateZ(${this.transform.rotation + rotation}deg)`);
this.renderer.setStyle(this.element.nativeElement, 'transform', transforms.join(' '));
});
}
}