Angular2:鼠标事件处理(相对于当前位置的移动)
Angular2: mouse event handling (movement relative to current position)
我的用户应该能够在 canvas 中通过鼠标移动(或旋转)对象。当鼠标事件发生时,屏幕坐标用于计算与最后一个事件的增量(方向和长度)。没什么特别的...
- mousedown(获取第一个坐标)
- mousemove(获取第n个坐标,计算deltaXY,移动对象deltaXY)
- 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
放在 document
或 window
上,否则如果您在 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 {}
我的用户应该能够在 canvas 中通过鼠标移动(或旋转)对象。当鼠标事件发生时,屏幕坐标用于计算与最后一个事件的增量(方向和长度)。没什么特别的...
- mousedown(获取第一个坐标)
- mousemove(获取第n个坐标,计算deltaXY,移动对象deltaXY)
- 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
放在 document
或 window
上,否则如果您在 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 {}