22

我一直在尝试使用 Angular 2 让可拖动的 div 工作。我使用angular2-examples repo 中的这个示例作为起点,只是真正调整代码以考虑删除该toRx()方法。该代码有效,但它不考虑mouseout事件。这意味着如果我点击一个 Draggable div,然后慢慢移动鼠标,该 div 将随着鼠标移动。但是,如果我将鼠标移动得太快,mouseout则会发送一个事件而不是一个mousemove事件,并且拖动停止。

mouseout在鼠标移动到触发事件后如何保持拖动?我已经尝试将mouseout事件流与事件流合并mousemove,以便将mouseout事件视为像事件一样mousemove,但这不起作用。

我正在使用 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 {
}
4

4 回答 4

29

我在RxJs How do deal with document events中找到了答案。问题的关键在于鼠标事件仅在鼠标悬停在该元素上时才会发送到该元素。所以我们确实希望mousedown事件仅限于特定元素,但我们必须跟踪全局 mousemovemouseup事件。这是新代码。注意@HostListener装饰器的使用,onMouseup并将onMousemove目标指定为document:mouseupdocument:mousemove。这就是将全局事件通过管道传输到 Rx 流中的方式。

HostListener的官方 angular2 文档没有提到这个target:eventName语法,但是这个 2.0.0-alpha.24 的旧 dart 文档确实提到了它。它似乎在 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';
            }
        });
    }
}
于 2016-03-29T17:10:24.120 回答
5

你可以使用这个: npm install ng2draggable

使用[ng2-draggable]="true",不要忘记="true"

你可以在这里找到它

https://github.com/cedvdb/ng2draggable

这是代码:

@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','');
        }
}
于 2016-10-13T16:53:18.533 回答
2

我对可拖动的弹出窗口有同样的问题,所以我在 mousedown 上将 mousemove 和 mouseup 事件添加到文档中,并在 mouseup 上删除它们。我使用Eric Martinez 的答案来动态添加和删除事件侦听器。

模板:

<div class="popup-win" (mousedown)="mousedown($event)"></div>

零件:

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

这是Plunker上的一个可运行示例。

于 2017-10-02T10:46:06.863 回答
1

您可以创建一个覆盖屏幕空间的大型 div。首先,此 div 的 z-index 低于您要拖动的 div。在接收 mousedown 时,您将 div 的 z-index 更改为高于拖动元素并在此 div 上接收鼠标移动事件。您可以 n 使用它来计算拖动元素的位置。然后,当您收到鼠标时,您可以停止并再次发送 div。

我最近在 Angular2 中编写了一个模块化的拖放框架。请试一试并提供反馈。

https://github.com/ivegotwings/ng2Draggable

但是,一旦触发 mouseout 事件,我就会停止拖动。

于 2016-03-29T02:01:37.890 回答