2016-03-17 36 views
9

Il mio utente deve essere in grado di spostare (o ruotare) un oggetto con il mouse su una tela. Quando si verificano eventi del mouse, le coordinate dello schermo vengono utilizzate per calcolare il delta (direzione e lunghezza) dell'ultimo evento. Niente di speciale ...Angolare2: gestione evento del mouse (movimento relativo alla posizione corrente)

  1. mousedown (vedere la prima coordinata)
  2. mousemove (ottenere ennesima coordinare, calcolare deltaXY, spostare l'oggetto da deltaXY)
  3. mouseup (uguale al passaggio 2 e arrestare il mousemove e mouseup gestione evento)

Dopo questa catena di eventi dovrebbe essere possibile ripetere la stessa azione.

Questo esempio obsoleto funziona come previsto, dopo aver rimosso le chiamate toRx. Ma qui il delta per la prima coordinata è determinata: github.com:rx-draggable

Ecco il mio sforzo per adattare il codice dall'esempio:

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

Funziona solo per un ciclo. Dopo l'evento mouseup ho ottenuto questo errore:

Uncaught EXCEPTION: Error during evaluation of "mousemove"

ORIGINAL EXCEPTION: ObjectUnsubscribedError

ERROR CONTEXT: EventEvaluationErrorContext

L'annullamento della sottoscrizione mousemove non funziona. L'errore si ripete per tutti i seguenti mousemoves.

Hai un'idea di cosa c'è di sbagliato nel mio codice? C'è un diverso approccio elegante per risolvere questo problema?

risposta

15

Credo che il problema si pone con la differenza tra unsubscribe() e remove(sub : Subscription) su un EventEmitter. Ma è possibile farlo senza l'uso di abbonamenti (tranne quelli creati da un @HostListener) e renderlo facile da leggere. Ho riscritto il tuo codice un po '. Potresti considerare di posizionare il tuo mouseupevent su document o window, altrimenti si ottiene un comportamento strano se si rilascia il mouse all'esterno dell'area di disegno.

Attenzione: codice non testato avanti

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

grazie, funziona alla grande. –

+0

il tuo vecchio codice, che usa 'remove' non ha funzionato, hai qualche idea del perché? Conosci una buona risorsa per apprendere quella roba reattiva con angular2? –

+0

@Meiko Mmm, non ho provato il 'remove', ma l'ho usato prima in questo modo e ha funzionato. Non ho buone risorse e sto imparando solo giocando :). Grazie per aver accettato. Contento di aver potuto aiutare – PierreDuc

3

Il problema che si ha è che il codice non è reattivo. Nella programmazione reattiva tutti i comportamenti dovrebbero essere definiti al momento della decorazione e solo una sottoscrizione è richiesta.

Ecco un esempio: 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 {}