Mon utilisateur doit pouvoir déplacer (ou faire pivoter) un objet avec la souris dans un canevas. Lorsque des événements de souris se produisent, les coordonnées d'écran sont utilisées pour calculer le delta (direction et longueur) du dernier événement. Rien de spécial...
Après cette chaîne d'événements, il devrait être possible de répéter la même action.
Cet exemple obsolète fonctionne comme prévu, après avoir supprimé les appels toRx
. Mais ici, le delta de la première coordonnée est déterminé: github.com:rx-draggable
Voici mes efforts pour adapter le code de l'exemple:
@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;
}
});
}
...
}
Cela ne fonctionne que pour un cycle. Après l'événement mouseup
, j'ai eu cette erreur:
EXCEPTION non détectée: erreur lors de l'évaluation de "mousemove"
EXCEPTION ORIGINALE: ObjectUnsubscribedError
CONTEXTE D'ERREUR: EventEvaluationErrorContext
L'annulation de l'abonnement mousemove
ne fonctionne pas. L'erreur se répète pour tous les mouvements de souris suivants.
Avez-vous une idée de ce qui ne va pas avec mon code? Existe-t-il une approche élégante différente pour résoudre ce problème?
Je crois que votre problème réside dans la différence entre unsubscribe()
et remove(sub : Subscription)
sur un EventEmitter
. Mais il est possible de le faire sans utiliser d'abonnements (sauf ceux créés par un @HostListener
) et le rendre facile à lire. J'ai un peu réécrit votre code. Vous pourriez envisager de placer votre mouseup
event
sur le document
ou window
, sinon vous obtiendrez un comportement étrange si vous relâchez votre souris en dehors du canevas.
Attention: code non testé à l'avance
@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;
}
}
Le problème que vous avez est que le code n'est pas réactif. Dans la programmation réactive, tous les comportements doivent être définis au moment de la décoration et un seul abonnement est requis.
Voici un exemple: translation/rotation de la souris Angular2/rxjs
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 {}