web-dev-qa-db-ja.com

Angular2:マウスイベント処理(現在位置を基準とした移動)

ユーザーは、キャンバス内でマウスを使用してオブジェクトを移動(または回転)できる必要があります。マウスイベントが発生すると、画面座標を使用して最後のイベントまでのデルタ(方向と長さ)が計算されます。特にない...

  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;
      }
    });
  }
  ...
}

1サイクルのみ機能します。 mouseupイベントの後、このエラーが発生しました。

キャッチされていない例外:「mousemove」の評価中にエラーが発生しました

元の例外:ObjectUnsubscribedError

エラーコンテキスト:EventEvaluationErrorContext

mousemoveサブスクリプションのキャンセルは機能しません。エラーは、次のすべてのmousemovesで繰り返されています。

私のコードの何が問題なのか考えていますか?この問題を解決する別のエレガントなアプローチはありますか?

14
Meiko Rachimow

あなたの問題は、EventEmitter上のunsubscribe()remove(sub : Subscription)の違いにあると思います。ただし、サブスクリプションを使用せずにそれを行うことは可能です(@HostListener)そして読みやすくします。あなたのコードを少し書き直しました。 mouseupまたはeventdocumentwindowを配置することを検討するかもしれません。そうしないと、キャンバスの外でマウスを離すと奇妙な動作をします。

警告:未テストのコード

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

あなたが持っている問題は、コードが反応しないということです。リアクティブプログラミングでは、すべての動作を装飾時に定義する必要があり、必要なサブスクリプションは1つだけです。

以下に例を示します: 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 {}
4
user55993