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

时间:2016-03-17 09:37:37

标签: typescript angular mouseevent rxjs eventemitter

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

  1. mousedown(获取第一个坐标)
  2. mousemove(获取第n个坐标,计算deltaXY,按deltaXY移动对象)
  3. mouseup(与步骤2相同,停止mousemove和mouseup事件处理)
  4. 在这一系列事件之后,应该可以重复相同的操作。

    这个过时的示例在删除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:评估“mousemove”时出错

         

    ORIGINAL EXCEPTION:ObjectUnsubscribedError

         

    ERROR CONTEXT:EventEvaluationErrorContext

    取消mousemove订阅不起作用。以下所有鼠标移动都会重复该错误。

    你知道我的代码有什么问题吗?是否有一种不同的优雅方法来解决这个问题?

2 个答案:

答案 0 :(得分:20)

我认为您的问题在于unsubscribe()remove(sub : Subscription)EventEmitter之间的差异。但是可以在不使用订阅的情况下完成(除了@HostListener创建的订阅之外)并使其易于阅读。我稍微改写了你的代码。您可以考虑将mouseup event放在documentwindow上,否则如果您将鼠标放在画布外,则会出现奇怪的行为。

警告:未经测试的代码

@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;
    }
}

答案 1 :(得分:4)

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

以下是一个示例: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 {}