为nzModal增加可拖拽功能 最后是RxJS实现版

2018-09-03  本文已影响0人  4f4e62418dff

实现功能很简单,就是nzModal像windows里的窗口一样,可以拖拽窗口以移动窗口位置。

思路也很简单。创建一个Directive,在Directive中先获取到modal-title的ElementRef对象和nz-modal的ElementRef。然后通过Render2为 modal-title 添加鼠标事件,再通过Render2修改nz-modal的style属性,达到跟随鼠标拖拽而移动的效果。

先创建一个新的Directive,并且引入ElementRef和Render2

import { Directive,ElementRef,Renderer2,AfterViewInit } from '@angular/core';

@Directive({
  selector: '[zmMovableModal]'
})
export class ZmMovableModalDirective {

  constructor(private elementRef:ElementRef,private render:Renderer2) {
  }
  ngAfterViewInit(){
  }
}

在你要事件拖拽的nzModal中增加这个新的Directive

  <nz-modal zmMovableModal nzTitle="Modal1" nzMask="false" (nzOnCancel)="hideModals()" [nzVisible]="modal1Visible"></nz-modal>
获取Element

首先我们在ZmMovableModalDirective中添加获取Title 和 nzModal的方法:

getModalElement(){
      return this.elementRef.nativeElement.querySelector('.ant-modal');
  }
  getModalTitleElment(){
    return this.elementRef.nativeElement.querySelector('.ant-modal-header');
  }

在ngAfterViewInit()里执行。

ngAfterViewInit(){
  let modalElement = this.getModalElement();
  let modalTitleElement = this.getModalTitleElment();
  console.log(modalElement)
  console.log(modalTitleElement);
}

如果控制台成功的打印出了对应的html代码段,就代表成功获取到了title和modal
如果是空的,可能是nzModal版本升级导致类名发生了变动,可以确定querySelector函数对应的class是否正确。

参数准备

在获取到title 和modal 后,我们先为实现拖拽功能准备一些参数:

//只有当鼠标点下之后,鼠标抬起之前,才能移动
private canMove :boolean = false;
//modal开始移动时的x,y坐标
  private modalX : number = 0;
  private modalY : number = 0;
//鼠标点下时,鼠标所在的坐标
  private mouseDownX :number = 0;
  private mouseDownY :number = 0;

有了这些准备后,我们开始为title添加鼠标事件。

鼠标事件

首先是鼠标点下的事件:

this.render.listen(modalTitleElement,'mousedown',function(event){
//记录modal和鼠标点击的起始坐标
      this.mouseDownX = event.clientX;
      this.mouseDownY = event.clientY;
//offsetLeft和offsetTop是相对窗口的坐标。
//我们的modal的移动正好也是基于整个窗口的。所以实现起来很简单。
      this.modalX = modalElement.offsetLeft;
      this.modalY = modalElement.offsetTop;
//将modal改为绝对定位。并且根据现在窗口的位置设置left和top
      this.render.setStyle(modalElement,"position","absolute");
      this.render.setStyle(modalElement,"top",`${this.modalY}px`);
      this.render.setStyle(modalElement,"left",`${this.modalX}px`);
//一切都准备好了 将状态设置 可移动。
      this.canMove = true;
}.bind(this));

然后设置mousemove的事件:

this.render.listen(modalTitleElement,'mousemove',function(event){
//只有在camMove状态下,才进行处理
      if(this.canMove){
//获得当前鼠标位置,并根据开始拖拽时的位置,计算出鼠标的偏移量
        let moveX = event.clientX - this.mouseDownX;
        let moveY = event.clientY - this.mouseDownY;
//鼠标的偏移量就是modal的偏移量。
//通过开始是modal的位置和偏移量计算Modal新的坐标
        let newModalX = this.modalX + moveX;
        let newModalY = this.modalY + moveY;
//将新坐标设置下去,这样modal就跟着鼠标移动了。
        this.render.setStyle(modalElement,"top",`${newModalY}px`);
        this.render.setStyle(modalElement,"left",`${newModalX}px`);
      }
    }.bind(this));

最后,是出挑抬起后的处理:

this.render.listen(modalTitleElement,'mouseup',function(event){
//将状态设置为不可移动
      this.canMove = false;
    }.bind(this));

这样nzModal拖拽的功能就实现了。

快速拖拽时产生BUG处理

虽然整体的功能实现了,但是还是存在一个问题。
在快速上下拖拽的时候,会有跟丢的现象。
应该是因为快速的鼠标飞出了title 的范围,那么加载在title上的mousemove事件就不会被调用,modal就不会移动了。
所以将mousemove事件绑定到更大的范围就处理了,
所以将

this.render.listen(modalTitleElement,'mousemove',function(event){
........

改成:

this.render.listen(this.elementRef.nativeElement,'mousemove',function(event){
............

无论怎么摇晃鼠标都不会跟丢了。

整体代码:
import { Directive,ElementRef,Renderer2,AfterViewInit } from '@angular/core';

@Directive({
  selector: '[zmMovableModal]'
})
export class ZmMovableModalDirective {
  private canMove :boolean = false;
  private modalX : number = 0;
  private modalY : number = 0;
  private mouseDownX :number = 0;
  private mouseDownY :number = 0;
  constructor(private elementRef:ElementRef,private render:Renderer2) {
  }
  ngAfterViewInit(){
    let modalElement = this.getModalElement();
    let modalTitleElement = this.getModalTitleElment();
    this.render.listen(modalTitleElement,'mousedown',function(event){
      this.mouseDownX = event.clientX;
      this.mouseDownY = event.clientY;
      this.modalX = modalElement.offsetLeft;
      this.modalY = modalElement.offsetTop;
      this.render.setStyle(modalElement,"position","absolute");
      this.render.setStyle(modalElement,"top",`${this.modalY}px`);
      this.render.setStyle(modalElement,"left",`${this.modalX}px`);
      this.canMove = true;
    }.bind(this));
    this.render.listen(modalTitleElement,'mouseup',function(event){
      this.canMove = false;
    }.bind(this));
   this.render.listen(this.elementRef.nativeElement,'mousemove',function(event){
      if(this.canMove){
        let moveX = event.clientX - this.mouseDownX;
        let moveY = event.clientY - this.mouseDownY;
        let newModalX = this.modalX + moveX;
        let newModalY = this.modalY + moveY;
        this.render.setStyle(modalElement,"top",`${newModalY}px`);
        this.render.setStyle(modalElement,"left",`${newModalX}px`);
      }
    }.bind(this));
  }
  getModalElement(){
      return this.elementRef.nativeElement.querySelector('.ant-modal');
  }
  getModalTitleElment(){
    return this.elementRef.nativeElement.querySelector('.ant-modal-header');
  }

}
新的问题

最近在使用中发现一个问题,就是如果nzTitle 是一个变量,那么虽然最后生成的html代码中有ant-modal-title这个div标签。但是在elementRef.nativeElement里却没相应的element。
这里还没找到原因。
想办先想办法绕过这个问题。

最好还是能找到ant-modal-header element的根本原因是什么,从根本上解决问题。

将getModalTitleElement()方法代码改成如下代码,解决了这个问题:

  getModalTitleElment(){
    // let header = this.elementRef.nativeElement.querySelector('.ant-modal-header');
    let element = document.createElement("div") as any;
    // this.render.setStyle(element,"background","orange");
    this.render.setStyle(element,"width","100%");
    this.render.setStyle(element,"height","60px");
    this.render.setStyle(element,"position","absolute");
    this.render.setStyle(element,"top","0");
    this.render.setStyle(element,"left","0");
    this.render.appendChild(this.modalElement,element);
    return element;
  }

这样,只要用户拖动最上方的一段都可以拖拽整个模态框了。

使用RxJS

最近深入学习一些RxJS的用法。正好拿这个指令练练手。
获取element的方法不用改变。
在ngAfterViewInit中

let modalElement = this.getModalElement();
tihs.modalElement = modalElement;
let modalTitleElement = this.getModalTitleElement();
const mouseDown = Observable.fromEvent<MouseEvent>(modalTitleElement,"mousedown");
const mouseUp = Observable.fromEvent<MouseEvent>(this.elementRef.nativeElement,"mouseup");
const mouseMove = Observable.fromEvent<MouseEvent>(this.elementRef.nativeElement,"mousemove");
mouseDown.subscrible(event=>{
  this.mouseDownX = event.clentX;
  this.mouseDownY = event.clentY;
  this.modalX = modalElement.offsetLeft;
  this.modalY = modalElement.offsetTop;
  this.render.setStyle(modalElement,"position","absolute");
  this.render.setStyle(modalElement,"top",`${this.modalY}px`);
  this.render.setStyle(modalElelment,"left",`${this.mdoalX}px`);
});
mouseDown.map(event=>mouseMove.takeUntil(mouseUp))
.concatAll()
.subscribe(e=>{
  let moveX = e.clientX - this.mouseDownX;
  let moveY = e.clientY - this.mouseDownY;
  let newModalX = this.modalX + moveX;
  let newModalY = this.modalY + moveY;
  this.render.setStyle(modalElement,"top",`${newModalY}px`);
  this.render.setStyle(modalElement,"left",`${newModalX}px`);
});

不知道有什么方法可以用一串RxJS代码就实现整个功能。未来想到办法会更新上去。

新的解决方案

将上面代码的两个RxJS代码块合并为一个:

mouseDown.map(event=>{
  this.modalX = modalElement.offsetLeft;
  this.modalY = modalElement.offsetTop;
  this.render.setStyle(modalElment,"position","absolute");
  this.render.setStyle(modalElement,"top",`${this.modalY}px`);
  this.render.setStyle(modalElement,"left",`$(this.modalX)px`);
}).map(event=>mouseMove.takeUntil(mouseUp))
.contactAll()
.withLatestFrom(mouseDown,(move,down)=>{
  return {x:move.clientX-down.clientX,y:move.clientY-down.clientY}
})
.subscribe(pos=>{
  this.render.setStyle(modalElement,"top",`${this.modalY+pos.y}px`);
  this.render.setStyle(modalElement,"left",`$(this.modalX+pos.x)px`);
})
上一篇 下一篇

猜你喜欢

热点阅读