Ionic3 Gesture pinch pan and rotate together

2019-06-15 19:08发布

问题:

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(' '));
    });
  }
}