Angular2:鼠标事件处理(相对于当前位置的移动)

Angular2: mouse event handling (movement relative to current position)

我的用户应该能够在 canvas 中通过鼠标移动(或旋转)对象。当鼠标事件发生时,屏幕坐标用于计算与最后一个事件的增量(方向和长度)。没什么特别的...

  1. mousedown(获取第一个坐标)
  2. mousemove(获取第n个坐标,计算deltaXY,移动对象deltaXY)
  3. mouseup(同步骤2,停止mousemove和mouseup事件处理)

在这一系列事件之后,应该可以重复相同的操作。

这个过时的示例在删除 toRx 调用后按预期工作。但是这里确定了第一个坐标的增量:github.com:rx-draggable

这是我改编示例代码的成果:

@Component({
  selector: 'home',
  providers: [Scene],
  template: '<canvas #canvas id="3dview"></canvas>'
})
export class Home {
  @ViewChild('canvas') canvas: ElementRef;
  private scene: Scene;
  private mousedrag = new EventEmitter();
  private mouseup   = new EventEmitter<MouseEvent>();
  private mousedown = new EventEmitter<MouseEvent>();
  private mousemove = new EventEmitter<MouseEvent>();
  private last: MouseEvent;
  private el: HTMLElement;

  @HostListener('mouseup', ['$event'])
  onMouseup(event: MouseEvent) { this.mouseup.emit(event); }

  @HostListener('mousemove', ['$event'])
  onMousemove(event: MouseEvent) { this.mousemove.emit(event); }

  constructor(@Inject(ElementRef) elementRef: ElementRef, scene: Scene) {
    this.el = elementRef.nativeElement;
    this.scene = scene;
  }

  @HostListener('mousedown', ['$event'])
  mouseHandling(event) {
    event.preventDefault();
    console.log('mousedown', event);
    this.last = event;
    this.mousemove.subscribe({next: evt => {
      console.log('mousemove.subscribe', evt);
      this.mousedrag.emit(evt);
    }});
    this.mouseup.subscribe({next: evt => {
      console.log('mousemove.subscribe', evt);
      this.mousedrag.emit(evt);
      this.mousemove.unsubscribe();
      this.mouseup.unsubscribe();
    }});
  }

  ngOnInit() {
    console.log('init');
    this.mousedrag.subscribe({
      next: evt => {
        console.log('mousedrag.subscribe', evt);
        this.scene.rotate(
            evt.clientX - this.last.clientX, 
            evt.clientY - this.last.clientY);
        this.last = evt;
      }
    });
  }
  ...
}

只作用一个周期。在 mouseup 事件之后我得到了这个错误:

Uncaught EXCEPTION: Error during evaluation of "mousemove"

ORIGINAL EXCEPTION: ObjectUnsubscribedError

ERROR CONTEXT: EventEvaluationErrorContext

取消mousemove订阅无效。以下所有鼠标移动都会重复错误。

你知道我的代码有什么问题吗?有没有其他优雅的方法来解决这个问题?

我认为您的问题在于 unsubscribe()remove(sub : Subscription)EventEmitter 上的区别。但是可以在不使用订阅(由 @HostListener 创建的订阅除外)的情况下做到这一点,并使其易于阅读。我已经稍微重写了你的代码。您可能会考虑将 mouseup event 放在 documentwindow 上,否则如果您在 canvas 之外释放鼠标,您会得到奇怪的行为。

警告:前方代码未经测试

@Component({
    selector: 'home',
    providers: [Scene],
    template: '<canvas #canvas id="3dview"></canvas>'
})
export class Home {
    @ViewChild('canvas') 
    canvas: ElementRef;

    private scene: Scene;
    private last: MouseEvent;
    private el: HTMLElement;

    private mouseDown : boolean = false;

    @HostListener('mouseup')
    onMouseup() {
        this.mouseDown = false;
    }

    @HostListener('mousemove', ['$event'])
    onMousemove(event: MouseEvent) {
        if(this.mouseDown) {
           this.scene.rotate(
              event.clientX - this.last.clientX,
              event.clientY - this.last.clientY
           );
           this.last = event;
        }
    }

    @HostListener('mousedown', ['$event'])
    onMousedown(event) {
        this.mouseDown = true;
        this.last = event;
    }

    constructor(elementRef: ElementRef, scene: Scene) {
        this.el = elementRef.nativeElement;
        this.scene = scene;
    }
}

您遇到的问题是代码不是反应性的。在响应式编程中,所有行为都应在装饰时定义,并且只需要一个订阅。

这里有一个例子:Angular2/rxjs mouse translation/rotation

import {Component, NgModule, OnInit, ViewChild} from '@angular/core'
import {BrowserModule, ElementRef, MouseEvent} from '@angular/platform-browser'
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMapTo';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/operator/combineLatest';
import 'rxjs/add/operator/startWith';

@Component({
  selector: 'my-app',
  styles: [`
  canvas{
    border: 1px solid red;
  }`],
  template: `
    <div>
      <h2>translate/Rotate by mouse</h2>
      <canvas #canvas id="3dview"></canvas>
      <p>Translate by delta: {{relativeTo$|async|json}}</p>
      <p>Rotate by angle: {{rotateToAngle$|async|json}}</p>
    </div>
  `
})
export class App extends OnInit {

    @ViewChild('canvas') 
    canvas: ElementRef;

    relativeTo$: Observable<{dx:number, dy:number, start: MouseEvent}>;
    rotateToAngle$: Observable<{angle:number, start: MouseEvent}>;

    ngOnInit() {
      const canvasNE = this.canvas.nativeElement;

      const mouseDown$ = Observable.fromEvent(canvasNE, 'mousedown');
      const mouseMove$ = Observable.fromEvent(canvasNE, 'mousemove');
      const mouseUp$ = Observable.fromEvent(canvasNE, 'mouseup');

      const moveUntilMouseUp$= mouseMove$.takeUntil(mouseUp$);
      const startRotate$ = mouseDown$.switchMapTo(moveUntilMouseUp$.startWith(null));

      const relativePoint = (start: MouseEvent, end: MouseEvent): {x:number, y:number} => 
      (start && end && {
        dx: start.clientX - end.clientX,
        dy: start.clientY - end.clientY,
        start: start
      } || {});

      this.relativeTo$ = startRotate$
        .combineLatest(mouseDown$)
        .map(arr => relativePoint(arr[0],arr[1]));

      this.rotateToAngle$ = this.relativeTo$
        .map((tr) => ({angle: Math.atan2(tr.dy, tr.dx), start: tr.start}));

//      this.relativeTo$.subscribe(console.log.bind(console,'rotate:'));
//      this.rotateToAngle$.subscribe(console.log.bind(console,'rotate 0:'));
    }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}