问题
I've been trying to get a draggable div working using Angular 2. I'm using this example from the angular2-examples repo as a starting point, only really adjusting the code to account for the removal of the toRx()
method. The code works, but it does not account for mouseout
events. This means that if I click on a Draggable div, and move the mouse slowly, the div will move with the mouse. But if I move the mouse too fast, a mouseout
event is sent instead of a mousemove
event, and the dragging stops.
How can I keep the drag going after the mouse is moved so far that a mouseout
event is fired? I've tried merging the mouseout
event stream with the mousemove
one, so that mouseout
events are treated just like mousemove
ones, but that doesn't work.
I'm using Angular 2.0.0-beta.12.
import {Component, Directive, HostListener, EventEmitter, ElementRef, OnInit} from 'angular2/core';
import {map, merge} from 'rxjs/Rx';
@Directive({
selector: '[draggable]'
})
export class Draggable implements OnInit {
mouseup = new EventEmitter();
mousedown = new EventEmitter();
mousemove = new EventEmitter();
mouseout = new EventEmitter();
@HostListener('mouseup', ['$event'])
onMouseup(event) {
this.mouseup.emit(event);
}
@HostListener('mousedown', ['$event'])
onMousedown(event) {
this.mousedown.emit(event);
return false; // Call preventDefault() on the event
}
@HostListener('mousemove', ['$event'])
onMousemove(event) {
this.mousemove.emit(event);
}
@HostListener('mouseout', ['$event'])
onMouseout(event) {
this.mouseout.emit(event);
return false; // Call preventDefault() on the event
}
constructor(public element: ElementRef) {
this.element.nativeElement.style.position = 'relative';
this.element.nativeElement.style.cursor = 'pointer';
map;
merge;
this.mousedrag = this.mousedown.map(event => {
return {
top: event.clientY - this.element.nativeElement.getBoundingClientRect().top
left: event.clientX - this.element.nativeElement.getBoundingClientRect().left,
};
})
.flatMap(
imageOffset => this.mousemove.merge(this.mouseout).map(pos => ({
top: pos.clientY - imageOffset.top,
left: pos.clientX - imageOffset.left
}))
.takeUntil(this.mouseup)
);
}
ngOnInit() {
this.mousedrag.subscribe({
next: pos => {
this.element.nativeElement.style.top = pos.top + 'px';
this.element.nativeElement.style.left = pos.left + 'px';
}
});
}
}
@Component({
selector: 'my-app',
template: `
<div draggable>
<h1>Hello, World!</h1>
</div>
`,
directives: [Draggable,],
})
export class AppComponent {
}
回答1:
I found the answer to this in RxJs How do deal with document events. The crux of the problem is that mouse events are only sent to an element when the mouse is over that element. So we do want the mousedown
event limited to specific element, but we have to track global mousemove
and mouseup
events. Here's the new code. Notice the use of the @HostListener
decorator on onMouseup
and onMousemove
specifies the target as document:mouseup
and document:mousemove
. This is how the global events are piped into the Rx stream.
The official angular2 documentation for HostListener doesn't mention this target:eventName
syntax, but this old dart documentation for 2.0.0-alpha.24 does mention it. It seems to still work in 2.0.0-beta.12.
@Directive({
selector: '[draggable]'
})
export class Draggable implements OnInit {
mouseup = new EventEmitter<MouseEvent>();
mousedown = new EventEmitter<MouseEvent>();
mousemove = new EventEmitter<MouseEvent>();
mousedrag: Observable<{top, left}>;
@HostListener('document:mouseup', ['$event'])
onMouseup(event: MouseEvent) {
this.mouseup.emit(event);
}
@HostListener('mousedown', ['$event'])
onMousedown(event: MouseEvent) {
this.mousedown.emit(event);
return false; // Call preventDefault() on the event
}
@HostListener('document:mousemove', ['$event'])
onMousemove(event: MouseEvent) {
this.mousemove.emit(event);
}
constructor(public element: ElementRef) {
this.element.nativeElement.style.position = 'relative';
this.element.nativeElement.style.cursor = 'pointer';
this.mousedrag = this.mousedown.map(event => {
return {
top: event.clientY - this.element.nativeElement.getBoundingClientRect().top
left: event.clientX - this.element.nativeElement.getBoundingClientRect().left,
};
})
.flatMap(
imageOffset => this.mousemove.map(pos => ({
top: pos.clientY - imageOffset.top,
left: pos.clientX - imageOffset.left
}))
.takeUntil(this.mouseup)
);
}
ngOnInit() {
this.mousedrag.subscribe({
next: pos => {
this.element.nativeElement.style.top = pos.top + 'px';
this.element.nativeElement.style.left = pos.left + 'px';
}
});
}
}
回答2:
You can use this : npm install ng2draggable
Use [ng2-draggable]="true"
, don't forget the ="true"
You can find it here
https://github.com/cedvdb/ng2draggable
Here is the code:
@Directive({
selector: '[ng2-draggable]'
})
export class Draggable implements OnInit{
topStart:number=0;
leftStart:number=0;
_allowDrag:boolean = true;
md:boolean;
constructor(public element: ElementRef) {}
ngOnInit(){
// css changes
if(this._allowDrag){
this.element.nativeElement.style.position = 'relative';
this.element.nativeElement.className += ' cursor-draggable';
}
}
@HostListener('mousedown', ['$event'])
onMouseDown(event:MouseEvent) {
if(event.button === 2)
return; // prevents right click drag, remove his if you don't want it
this.md = true;
this.topStart = event.clientY - this.element.nativeElement.style.top.replace('px','');
this.leftStart = event.clientX - this.element.nativeElement.style.left.replace('px','');
}
@HostListener('document:mouseup')
onMouseUp(event:MouseEvent) {
this.md = false;
}
@HostListener('document:mousemove', ['$event'])
onMouseMove(event:MouseEvent) {
if(this.md && this._allowDrag){
this.element.nativeElement.style.top = (event.clientY - this.topStart) + 'px';
this.element.nativeElement.style.left = (event.clientX - this.leftStart) + 'px';
}
}
@HostListener('touchstart', ['$event'])
onTouchStart(event:TouchEvent) {
this.md = true;
this.topStart = event.changedTouches[0].clientY - this.element.nativeElement.style.top.replace('px','');
this.leftStart = event.changedTouches[0].clientX - this.element.nativeElement.style.left.replace('px','');
event.stopPropagation();
}
@HostListener('document:touchend')
onTouchEnd() {
this.md = false;
}
@HostListener('document:touchmove', ['$event'])
onTouchMove(event:TouchEvent) {
if(this.md && this._allowDrag){
this.element.nativeElement.style.top = ( event.changedTouches[0].clientY - this.topStart ) + 'px';
this.element.nativeElement.style.left = ( event.changedTouches[0].clientX - this.leftStart ) + 'px';
}
event.stopPropagation();
}
@Input('ng2-draggable')
set allowDrag(value:boolean){
this._allowDrag = value;
if(this._allowDrag)
this.element.nativeElement.className += ' cursor-draggable';
else
this.element.nativeElement.className = this.element.nativeElement.className
.replace(' cursor-draggable','');
}
}
回答3:
You could create a large div that covers the screen real estate. To start with this div has a lower z-index than the div you want to drag. On receiving mousedown you change the z-index of the div to be higher than the drag-element and receive mouse move events on this div. You could the n use that to compute the position of the drag-element. You can then stop and send the div back again when you receive a mouse up.
I have recently written a modular drag and drop framework in Angular2. Please give it a try and provide feedback.
https://github.com/ivegotwings/ng2Draggable
However, I stop the drag once the mouseout event is fired.
回答4:
I have same problem with draggable popup, so I add mousemove and mouseup events to document on mousedown, and remove them on mouseup. I use Eric Martinez's answer for add and remove event listener dynamically.
Template:
<div class="popup-win" (mousedown)="mousedown($event)"></div>
Component:
constructor(private elementRef: ElementRef,
private renderer: Renderer2) {}
mousedown(event: any) {
this.xStartElementPoint = this.curX;
this.yStartElementPoint = this.curY;
this.xStartMousePoint = event.pageX;
this.yStartMousePoint = event.pageY;
this.mousemoveEvent = this.renderer.listen("document", "mousemove", this.dragging);
this.mouseupEvent = this.renderer.listen("document", "mouseup", this.mouseup);
}
dragging(event: any) {
this.curX = this.xStartElementPoint + (event.pageX - this.xStartMousePoint);
this.curY = this.yStartElementPoint + (event.pageY - this.yStartMousePoint);
}
mouseup(event: any) {
// Remove listeners
this.mousemoveEvent();
this.mouseupEvent();
}
Here's a runnable example on Plunker.
来源:https://stackoverflow.com/questions/36273791/how-to-implement-a-draggable-div-in-angular-2-using-rx